tom/use-event-listener
This commit is contained in:
@@ -9,6 +9,7 @@ import { depths } from "@shared/styles";
|
|||||||
import Button from "~/components/Button";
|
import Button from "~/components/Button";
|
||||||
import Fade from "~/components/Fade";
|
import Fade from "~/components/Fade";
|
||||||
import Flex from "~/components/Flex";
|
import Flex from "~/components/Flex";
|
||||||
|
import useEventListener from "~/hooks/useEventListener";
|
||||||
import useMobile from "~/hooks/useMobile";
|
import useMobile from "~/hooks/useMobile";
|
||||||
import useStores from "~/hooks/useStores";
|
import useStores from "~/hooks/useStores";
|
||||||
import { supportsPassiveListener } from "~/utils/browser";
|
import { supportsPassiveListener } from "~/utils/browser";
|
||||||
@@ -29,19 +30,17 @@ function Header({ breadcrumb, title, actions, hasSidebar }: Props) {
|
|||||||
const passThrough = !actions && !breadcrumb && !title;
|
const passThrough = !actions && !breadcrumb && !title;
|
||||||
|
|
||||||
const [isScrolled, setScrolled] = React.useState(false);
|
const [isScrolled, setScrolled] = React.useState(false);
|
||||||
const handleScroll = React.useCallback(
|
const handleScroll = React.useMemo(
|
||||||
throttle(() => setScrolled(window.scrollY > 75), 50),
|
() => throttle(() => setScrolled(window.scrollY > 75), 50),
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEventListener(
|
||||||
window.addEventListener(
|
"scroll",
|
||||||
"scroll",
|
handleScroll,
|
||||||
handleScroll,
|
window,
|
||||||
supportsPassiveListener ? { passive: true } : false
|
supportsPassiveListener ? { passive: true } : { capture: false }
|
||||||
);
|
);
|
||||||
return () => window.removeEventListener("scroll", handleScroll);
|
|
||||||
}, [handleScroll]);
|
|
||||||
|
|
||||||
const handleClickTitle = React.useCallback(() => {
|
const handleClickTitle = React.useCallback(() => {
|
||||||
window.scrollTo({
|
window.scrollTo({
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Portal } from "react-portal";
|
|||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { depths } from "@shared/styles";
|
import { depths } from "@shared/styles";
|
||||||
import useComponentSize from "~/hooks/useComponentSize";
|
import useComponentSize from "~/hooks/useComponentSize";
|
||||||
|
import useEventListener from "~/hooks/useEventListener";
|
||||||
import useMediaQuery from "~/hooks/useMediaQuery";
|
import useMediaQuery from "~/hooks/useMediaQuery";
|
||||||
import useViewportHeight from "~/hooks/useViewportHeight";
|
import useViewportHeight from "~/hooks/useViewportHeight";
|
||||||
|
|
||||||
@@ -164,25 +165,15 @@ const FloatingToolbar = React.forwardRef(
|
|||||||
props,
|
props,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEventListener("mouseup", () => {
|
||||||
const handleMouseDown = () => {
|
setSelectingText(false);
|
||||||
if (!props.active) {
|
});
|
||||||
setSelectingText(true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseUp = () => {
|
useEventListener("mousedown", () => {
|
||||||
setSelectingText(false);
|
if (!props.active) {
|
||||||
};
|
setSelectingText(true);
|
||||||
|
}
|
||||||
window.addEventListener("mousedown", handleMouseDown);
|
});
|
||||||
window.addEventListener("mouseup", handleMouseUp);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("mousedown", handleMouseDown);
|
|
||||||
window.removeEventListener("mouseup", handleMouseUp);
|
|
||||||
};
|
|
||||||
}, [props.active]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<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 { throttle } from "lodash";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import useEventListener from "./useEventListener";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mouse position as a tuple of [x, y]
|
* Mouse position as a tuple of [x, y]
|
||||||
@@ -17,15 +18,15 @@ export const useMousePosition = () => {
|
|||||||
0,
|
0,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const updateMousePosition = throttle((ev: MouseEvent) => {
|
const updateMousePosition = React.useMemo(
|
||||||
setMousePosition([ev.clientX, ev.clientY]);
|
() =>
|
||||||
}, 200);
|
throttle((ev: MouseEvent) => {
|
||||||
|
setMousePosition([ev.clientX, ev.clientY]);
|
||||||
|
}, 200),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEventListener("mousemove", updateMousePosition);
|
||||||
window.addEventListener("mousemove", updateMousePosition);
|
|
||||||
|
|
||||||
return () => window.removeEventListener("mousemove", updateMousePosition);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return mousePosition;
|
return mousePosition;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import useEventListener from "./useEventListener";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook to return page visibility state.
|
* Hook to return page visibility state.
|
||||||
@@ -8,13 +9,11 @@ import * as React from "react";
|
|||||||
export default function usePageVisibility(): boolean {
|
export default function usePageVisibility(): boolean {
|
||||||
const [visible, setVisible] = React.useState(true);
|
const [visible, setVisible] = React.useState(true);
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEventListener(
|
||||||
const handleVisibilityChange = () => setVisible(!document.hidden);
|
"visibilitychange",
|
||||||
|
() => setVisible(!document.hidden),
|
||||||
|
document
|
||||||
|
);
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
return visible;
|
return visible;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Primitive } from "utility-types";
|
import { Primitive } from "utility-types";
|
||||||
import Storage from "~/utils/Storage";
|
import Storage from "~/utils/Storage";
|
||||||
|
import useEventListener from "./useEventListener";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A hook with the same API as `useState` that persists its value locally and
|
* 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
|
// Listen to the key changing in other tabs so we can keep UI in sync
|
||||||
React.useEffect(() => {
|
useEventListener("storage", (event: StorageEvent) => {
|
||||||
const updateValue = (event: any) => {
|
if (event.key === key && event.newValue) {
|
||||||
if (event.key === key && event.newValue) {
|
setStoredValue(JSON.parse(event.newValue));
|
||||||
setStoredValue(JSON.parse(event.newValue));
|
}
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("storage", updateValue);
|
|
||||||
return () => window.removeEventListener("storage", updateValue);
|
|
||||||
}, [key]);
|
|
||||||
|
|
||||||
return [storedValue, setValue];
|
return [storedValue, setValue];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,35 @@
|
|||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import * as React from "react";
|
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() {
|
export default function useWindowSize() {
|
||||||
const [windowSize, setWindowSize] = React.useState({
|
const [windowSize, setWindowSize] = React.useState({
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
const handleResize = React.useMemo(
|
||||||
// Handler to call on window resize
|
() =>
|
||||||
const handleResize = debounce(() => {
|
debounce(() => {
|
||||||
// Set window width/height to state
|
// Set window width/height to state
|
||||||
setWindowSize({
|
setWindowSize({
|
||||||
width: window.innerWidth,
|
width: window.innerWidth,
|
||||||
height: window.innerHeight,
|
height: window.innerHeight,
|
||||||
});
|
});
|
||||||
}, 100);
|
}, 100),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
// Add event listener
|
useEventListener("resize", handleResize);
|
||||||
window.addEventListener("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;
|
return windowSize;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user