tom/use-event-listener
This commit is contained in:
@@ -9,6 +9,7 @@ import { depths } from "@shared/styles";
|
||||
import Button from "~/components/Button";
|
||||
import Fade from "~/components/Fade";
|
||||
import Flex from "~/components/Flex";
|
||||
import useEventListener from "~/hooks/useEventListener";
|
||||
import useMobile from "~/hooks/useMobile";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { supportsPassiveListener } from "~/utils/browser";
|
||||
@@ -29,19 +30,17 @@ function Header({ breadcrumb, title, actions, hasSidebar }: Props) {
|
||||
const passThrough = !actions && !breadcrumb && !title;
|
||||
|
||||
const [isScrolled, setScrolled] = React.useState(false);
|
||||
const handleScroll = React.useCallback(
|
||||
throttle(() => setScrolled(window.scrollY > 75), 50),
|
||||
const handleScroll = React.useMemo(
|
||||
() => throttle(() => setScrolled(window.scrollY > 75), 50),
|
||||
[]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener(
|
||||
"scroll",
|
||||
handleScroll,
|
||||
supportsPassiveListener ? { passive: true } : false
|
||||
);
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, [handleScroll]);
|
||||
useEventListener(
|
||||
"scroll",
|
||||
handleScroll,
|
||||
window,
|
||||
supportsPassiveListener ? { passive: true } : { capture: false }
|
||||
);
|
||||
|
||||
const handleClickTitle = React.useCallback(() => {
|
||||
window.scrollTo({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Portal } from "react-portal";
|
||||
import styled from "styled-components";
|
||||
import { depths } from "@shared/styles";
|
||||
import useComponentSize from "~/hooks/useComponentSize";
|
||||
import useEventListener from "~/hooks/useEventListener";
|
||||
import useMediaQuery from "~/hooks/useMediaQuery";
|
||||
import useViewportHeight from "~/hooks/useViewportHeight";
|
||||
|
||||
@@ -164,25 +165,15 @@ const FloatingToolbar = React.forwardRef(
|
||||
props,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleMouseDown = () => {
|
||||
if (!props.active) {
|
||||
setSelectingText(true);
|
||||
}
|
||||
};
|
||||
useEventListener("mouseup", () => {
|
||||
setSelectingText(false);
|
||||
});
|
||||
|
||||
const handleMouseUp = () => {
|
||||
setSelectingText(false);
|
||||
};
|
||||
|
||||
window.addEventListener("mousedown", handleMouseDown);
|
||||
window.addEventListener("mouseup", handleMouseUp);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mousedown", handleMouseDown);
|
||||
window.removeEventListener("mouseup", handleMouseUp);
|
||||
};
|
||||
}, [props.active]);
|
||||
useEventListener("mousedown", () => {
|
||||
if (!props.active) {
|
||||
setSelectingText(true);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
|
||||
38
app/hooks/useEventListener.ts
Normal file
38
app/hooks/useEventListener.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import * as React from "react";
|
||||
|
||||
/**
|
||||
* Helper to remove plumbing involved with adding and removing an event listener
|
||||
* in components.
|
||||
*
|
||||
* @param eventName The name of the event to listen to.
|
||||
* @param handler The handler to call when the event is triggered.
|
||||
* @param element The element to attach the event listener to.
|
||||
* @param options The options to pass to the event listener.
|
||||
*/
|
||||
export default function useEventListener<T extends EventListener>(
|
||||
eventName: string,
|
||||
handler: T,
|
||||
element: Window | Node = window,
|
||||
options: AddEventListenerOptions = {}
|
||||
) {
|
||||
const savedHandler = React.useRef<T>();
|
||||
const { capture, passive, once } = options;
|
||||
|
||||
React.useEffect(() => {
|
||||
savedHandler.current = handler;
|
||||
}, [handler]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const isSupported = element && element.addEventListener;
|
||||
if (!isSupported) {
|
||||
return;
|
||||
}
|
||||
|
||||
const eventListener: EventListener = (event) =>
|
||||
savedHandler.current?.(event);
|
||||
|
||||
const opts = { capture, passive, once };
|
||||
element.addEventListener(eventName, eventListener, opts);
|
||||
return () => element.removeEventListener(eventName, eventListener, opts);
|
||||
}, [eventName, element, capture, passive, once]);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { throttle } from "lodash";
|
||||
import * as React from "react";
|
||||
import useEventListener from "./useEventListener";
|
||||
|
||||
/**
|
||||
* Mouse position as a tuple of [x, y]
|
||||
@@ -17,15 +18,15 @@ export const useMousePosition = () => {
|
||||
0,
|
||||
]);
|
||||
|
||||
const updateMousePosition = throttle((ev: MouseEvent) => {
|
||||
setMousePosition([ev.clientX, ev.clientY]);
|
||||
}, 200);
|
||||
const updateMousePosition = React.useMemo(
|
||||
() =>
|
||||
throttle((ev: MouseEvent) => {
|
||||
setMousePosition([ev.clientX, ev.clientY]);
|
||||
}, 200),
|
||||
[]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener("mousemove", updateMousePosition);
|
||||
|
||||
return () => window.removeEventListener("mousemove", updateMousePosition);
|
||||
}, []);
|
||||
useEventListener("mousemove", updateMousePosition);
|
||||
|
||||
return mousePosition;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from "react";
|
||||
import useEventListener from "./useEventListener";
|
||||
|
||||
/**
|
||||
* Hook to return page visibility state.
|
||||
@@ -8,13 +9,11 @@ import * as React from "react";
|
||||
export default function usePageVisibility(): boolean {
|
||||
const [visible, setVisible] = React.useState(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleVisibilityChange = () => setVisible(!document.hidden);
|
||||
useEventListener(
|
||||
"visibilitychange",
|
||||
() => setVisible(!document.hidden),
|
||||
document
|
||||
);
|
||||
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
return () => {
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
};
|
||||
}, []);
|
||||
return visible;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
import { Primitive } from "utility-types";
|
||||
import Storage from "~/utils/Storage";
|
||||
import useEventListener from "./useEventListener";
|
||||
|
||||
/**
|
||||
* A hook with the same API as `useState` that persists its value locally and
|
||||
@@ -36,16 +37,11 @@ export default function usePersistedState(
|
||||
};
|
||||
|
||||
// Listen to the key changing in other tabs so we can keep UI in sync
|
||||
React.useEffect(() => {
|
||||
const updateValue = (event: any) => {
|
||||
if (event.key === key && event.newValue) {
|
||||
setStoredValue(JSON.parse(event.newValue));
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("storage", updateValue);
|
||||
return () => window.removeEventListener("storage", updateValue);
|
||||
}, [key]);
|
||||
useEventListener("storage", (event: StorageEvent) => {
|
||||
if (event.key === key && event.newValue) {
|
||||
setStoredValue(JSON.parse(event.newValue));
|
||||
}
|
||||
});
|
||||
|
||||
return [storedValue, setValue];
|
||||
}
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
import { debounce } from "lodash";
|
||||
import * as React from "react";
|
||||
import useEventListener from "./useEventListener";
|
||||
|
||||
/**
|
||||
* A debounced hook that listens to the window resize event and returns the
|
||||
* size of the current window.
|
||||
*
|
||||
* @returns An object containing width and height of the current window
|
||||
*/
|
||||
export default function useWindowSize() {
|
||||
const [windowSize, setWindowSize] = React.useState({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
// Handler to call on window resize
|
||||
const handleResize = debounce(() => {
|
||||
// Set window width/height to state
|
||||
setWindowSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
}, 100);
|
||||
const handleResize = React.useMemo(
|
||||
() =>
|
||||
debounce(() => {
|
||||
// Set window width/height to state
|
||||
setWindowSize({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
});
|
||||
}, 100),
|
||||
[]
|
||||
);
|
||||
|
||||
// Add event listener
|
||||
window.addEventListener("resize", handleResize);
|
||||
useEventListener("resize", handleResize);
|
||||
|
||||
// Call handler right away so state gets updated with initial window size
|
||||
handleResize();
|
||||
|
||||
// Call handler right away so state gets updated with initial window size
|
||||
handleResize();
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, []);
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user