From e626a3fc3776f06ad3b92d8f1afdb8586bf66a13 Mon Sep 17 00:00:00 2001 From: Maxwell Barvian Date: Sun, 15 Dec 2024 13:22:54 -0800 Subject: [PATCH] Use useSyncExternalStore for React hooks --- .changeset/famous-drinks-watch.md | 5 +++ packages/react/src/index.tsx | 43 ++++++++++---------- site/src/components/Supported.tsx | 65 +++++++++++++++---------------- 3 files changed, 57 insertions(+), 56 deletions(-) create mode 100644 .changeset/famous-drinks-watch.md diff --git a/.changeset/famous-drinks-watch.md b/.changeset/famous-drinks-watch.md new file mode 100644 index 00000000..6bdaddd3 --- /dev/null +++ b/.changeset/famous-drinks-watch.md @@ -0,0 +1,5 @@ +--- +'@number-flow/react': patch +--- + +Use useSyncExternalStore for hooks diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx index faf2c566..d2bfb804 100644 --- a/packages/react/src/index.tsx +++ b/packages/react/src/index.tsx @@ -9,7 +9,7 @@ import { formatToData, type Data, NumberFlowLite, - prefersReducedMotion, + prefersReducedMotion as _prefersReducedMotion, canAnimate as _canAnimate, define } from 'number-flow' @@ -275,28 +275,25 @@ export function NumberFlowGroup({ children }: { children: React.ReactNode }) { return {children} } -// SSR-safe canAnimate +export const useIsSupported = () => + React.useSyncExternalStore( + () => () => {}, // this value doesn't change, but it's useful to specify a different SSR value: + () => _canAnimate, + () => false + ) +export const usePrefersReducedMotion = () => + React.useSyncExternalStore( + (cb) => { + _prefersReducedMotion?.addEventListener('change', cb) + return () => _prefersReducedMotion?.removeEventListener('change', cb) + }, + () => _prefersReducedMotion!.matches, + () => false + ) + export function useCanAnimate({ respectMotionPreference = true } = {}) { - const [canAnimate, setCanAnimate] = React.useState(false) - const [reducedMotion, setReducedMotion] = React.useState(false) - - // Handle SSR: - React.useEffect(() => { - setCanAnimate(_canAnimate) - setReducedMotion(prefersReducedMotion?.matches ?? false) - }, []) - - // Listen for reduced motion changes if needed: - React.useEffect(() => { - if (!respectMotionPreference) return - const onChange = ({ matches }: MediaQueryListEvent) => { - setReducedMotion(matches) - } - prefersReducedMotion?.addEventListener('change', onChange) - return () => { - prefersReducedMotion?.removeEventListener('change', onChange) - } - }, [respectMotionPreference]) + const isSupported = useIsSupported() + const reducedMotion = usePrefersReducedMotion() - return canAnimate && (!respectMotionPreference || !reducedMotion) + return isSupported && (!respectMotionPreference || !reducedMotion) } diff --git a/site/src/components/Supported.tsx b/site/src/components/Supported.tsx index fab90f07..ee0dc809 100644 --- a/site/src/components/Supported.tsx +++ b/site/src/components/Supported.tsx @@ -1,41 +1,40 @@ -import { useCanAnimate } from '@number-flow/react' +import { useIsSupported, usePrefersReducedMotion } from '@number-flow/react' import clsx from 'clsx/lite' import Link from './Link' export default function Supported() { - const canAnimate = useCanAnimate({ respectMotionPreference: false }) - const reducedMotion = useCanAnimate({ respectMotionPreference: true }) + const isSupported = useIsSupported() + const reducedMotion = usePrefersReducedMotion() + if (isSupported && !reducedMotion) return null return ( - !reducedMotion && ( - <> - {/* The only way I could get the blend mode working was to separate the divs which requires fixed sizes */} -
-
- {!canAnimate ? ( -

- Your browser doesn't{' '} - - support NumberFlow’s animations - -

- ) : ( -

Reduce motion is on

- )} -
- - ) + <> + {/* The only way I could get the blend mode working was to separate the divs which requires fixed sizes */} +
+
+ {reducedMotion ? ( +

Reduce motion is on

+ ) : ( +

+ Your browser doesn't{' '} + + support NumberFlow’s animations + +

+ )} +
+ ) }