Skip to content

Commit e626a3f

Browse files
committedDec 15, 2024·
Use useSyncExternalStore for React hooks
1 parent 4f759f0 commit e626a3f

File tree

3 files changed

+57
-56
lines changed

3 files changed

+57
-56
lines changed
 

‎.changeset/famous-drinks-watch.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@number-flow/react': patch
3+
---
4+
5+
Use useSyncExternalStore for hooks

‎packages/react/src/index.tsx

+20-23
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
formatToData,
1010
type Data,
1111
NumberFlowLite,
12-
prefersReducedMotion,
12+
prefersReducedMotion as _prefersReducedMotion,
1313
canAnimate as _canAnimate,
1414
define
1515
} from 'number-flow'
@@ -275,28 +275,25 @@ export function NumberFlowGroup({ children }: { children: React.ReactNode }) {
275275
return <NumberFlowGroupContext.Provider value={value}>{children}</NumberFlowGroupContext.Provider>
276276
}
277277

278-
// SSR-safe canAnimate
278+
export const useIsSupported = () =>
279+
React.useSyncExternalStore(
280+
() => () => {}, // this value doesn't change, but it's useful to specify a different SSR value:
281+
() => _canAnimate,
282+
() => false
283+
)
284+
export const usePrefersReducedMotion = () =>
285+
React.useSyncExternalStore(
286+
(cb) => {
287+
_prefersReducedMotion?.addEventListener('change', cb)
288+
return () => _prefersReducedMotion?.removeEventListener('change', cb)
289+
},
290+
() => _prefersReducedMotion!.matches,
291+
() => false
292+
)
293+
279294
export function useCanAnimate({ respectMotionPreference = true } = {}) {
280-
const [canAnimate, setCanAnimate] = React.useState(false)
281-
const [reducedMotion, setReducedMotion] = React.useState(false)
282-
283-
// Handle SSR:
284-
React.useEffect(() => {
285-
setCanAnimate(_canAnimate)
286-
setReducedMotion(prefersReducedMotion?.matches ?? false)
287-
}, [])
288-
289-
// Listen for reduced motion changes if needed:
290-
React.useEffect(() => {
291-
if (!respectMotionPreference) return
292-
const onChange = ({ matches }: MediaQueryListEvent) => {
293-
setReducedMotion(matches)
294-
}
295-
prefersReducedMotion?.addEventListener('change', onChange)
296-
return () => {
297-
prefersReducedMotion?.removeEventListener('change', onChange)
298-
}
299-
}, [respectMotionPreference])
295+
const isSupported = useIsSupported()
296+
const reducedMotion = usePrefersReducedMotion()
300297

301-
return canAnimate && (!respectMotionPreference || !reducedMotion)
298+
return isSupported && (!respectMotionPreference || !reducedMotion)
302299
}

‎site/src/components/Supported.tsx

+32-33
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,40 @@
1-
import { useCanAnimate } from '@number-flow/react'
1+
import { useIsSupported, usePrefersReducedMotion } from '@number-flow/react'
22
import clsx from 'clsx/lite'
33
import Link from './Link'
44

55
export default function Supported() {
6-
const canAnimate = useCanAnimate({ respectMotionPreference: false })
7-
const reducedMotion = useCanAnimate({ respectMotionPreference: true })
6+
const isSupported = useIsSupported()
7+
const reducedMotion = usePrefersReducedMotion()
8+
if (isSupported && !reducedMotion) return null
89

910
return (
10-
!reducedMotion && (
11-
<>
12-
{/* The only way I could get the blend mode working was to separate the divs which requires fixed sizes */}
13-
<div
14-
role="presentation"
15-
className={clsx(
16-
canAnimate ? 'h-11.5' : 'h-16.5',
17-
'~top-4/8 fixed left-1/2 z-40 -ml-36 w-72 rounded-lg mix-blend-multiply shadow-lg shadow-amber-500/10 dark:shadow-amber-950/20'
18-
)}
19-
/>
20-
<div
21-
role="alert"
22-
className={clsx(
23-
canAnimate ? 'h-11.5' : 'h-16.5',
24-
'~top-4/8 prose prose-current fixed left-1/2 z-50 -ml-36 w-72 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-center text-sm text-amber-800 dark:border-amber-800/30 dark:bg-[#271202] dark:text-amber-50'
25-
)}
26-
>
27-
{!canAnimate ? (
28-
<p>
29-
Your browser doesn't{' '}
30-
<Link href="https://caniuse.com/mdn-css_types_mod">
31-
support NumberFlow’s animations
32-
</Link>
33-
</p>
34-
) : (
35-
<p>Reduce motion is on</p>
36-
)}
37-
</div>
38-
</>
39-
)
11+
<>
12+
{/* The only way I could get the blend mode working was to separate the divs which requires fixed sizes */}
13+
<div
14+
role="presentation"
15+
className={clsx(
16+
reducedMotion ? 'h-11.5' : 'h-16.5',
17+
'~top-4/8 fixed left-1/2 z-40 -ml-36 w-72 rounded-lg mix-blend-multiply shadow-lg shadow-amber-500/10 dark:shadow-amber-950/20'
18+
)}
19+
/>
20+
<div
21+
role="alert"
22+
className={clsx(
23+
reducedMotion ? 'h-11.5' : 'h-16.5',
24+
'~top-4/8 prose prose-current fixed left-1/2 z-50 -ml-36 w-72 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-center text-sm text-amber-800 dark:border-amber-800/30 dark:bg-[#271202] dark:text-amber-50'
25+
)}
26+
>
27+
{reducedMotion ? (
28+
<p>Reduce motion is on</p>
29+
) : (
30+
<p>
31+
Your browser doesn't{' '}
32+
<Link href="https://caniuse.com/mdn-css_types_mod">
33+
support NumberFlow’s animations
34+
</Link>
35+
</p>
36+
)}
37+
</div>
38+
</>
4039
)
4140
}

0 commit comments

Comments
 (0)
Please sign in to comment.