tom/use-event-listener

This commit is contained in:
Tom Moor
2022-04-17 11:00:28 -07:00
parent e4e98286f4
commit 2fb0182e16
7 changed files with 99 additions and 68 deletions

View 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]);
}

View File

@@ -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;
};

View File

@@ -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;
}

View File

@@ -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];
}

View File

@@ -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;
}