Skip to content

Commit 537928d

Browse files
committed
fix: Check if adding to registry in useInsertionEffect fails and fall back to useLayoutEffect.
1 parent 63b14a0 commit 537928d

File tree

1 file changed

+41
-10
lines changed

1 file changed

+41
-10
lines changed

src/style.js

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,46 @@ import React from 'react'
22
import { useStyleRegistry, createStyleRegistry } from './stylesheet-registry'
33
import { computeId } from './lib/hash'
44

5-
// Opt-into the new `useInsertionEffect` API in React 18, fallback to `useLayoutEffect`.
6-
// https://github.com/reactwg/react-18/discussions/110
7-
const useInsertionEffect = React.useInsertionEffect || React.useLayoutEffect
5+
function useRegistry(registry, props) {
6+
const addToRegistry = React.useCallback(() => {
7+
registry.add(props)
8+
return () => {
9+
registry.remove(props)
10+
}
11+
// props.children can be string[], will be striped since id is identical
12+
}, [props.id, String(props.dynamic)])
13+
14+
const addedDuringInsertionEffectRef = React.useRef(false)
15+
16+
// Opt-into the new `useInsertionEffect` API in React 18.
17+
// https://github.com/reactwg/react-18/discussions/110
18+
if (React.useInsertionEffect) {
19+
React.useInsertionEffect(() => {
20+
// `useInsertionEffect` can be called while `document.head` is null
21+
// (e.g. when `hydrateRoot` is passed `document` as the first argument and there
22+
// is a hydration mismatch). This causes `registry.add` to throw an error,
23+
// preventing React from recovering during client rendering after the mismatch
24+
try {
25+
const removeFromRegistry = addToRegistry()
26+
addedDuringInsertionEffectRef.current = true
27+
return removeFromRegistry
28+
} catch (_) {}
29+
}, [addToRegistry])
30+
}
31+
32+
// Use `useLayoutEffect` as a fallback for React 16/17 and in case `useInsertionEffect`
33+
// fails
34+
React.useLayoutEffect(() => {
35+
if (addedDuringInsertionEffectRef.current) {
36+
// `useLayoutEffect` is called after `useInsertionEffect`.
37+
// Reset the ref in case a future failure in `useInsertionEffect` occurs
38+
addedDuringInsertionEffectRef.current = false
39+
return
40+
}
41+
42+
return addToRegistry()
43+
}, [addToRegistry])
44+
}
845

946
const defaultRegistry =
1047
typeof window !== 'undefined' ? createStyleRegistry() : undefined
@@ -21,13 +58,7 @@ export default function JSXStyle(props) {
2158
return null
2259
}
2360

24-
useInsertionEffect(() => {
25-
registry.add(props)
26-
return () => {
27-
registry.remove(props)
28-
}
29-
// props.children can be string[], will be striped since id is identical
30-
}, [props.id, String(props.dynamic)])
61+
useRegistry(registry, props)
3162

3263
return null
3364
}

0 commit comments

Comments
 (0)