Skip to content

Commit 70b8346

Browse files
Add React 19-compatible render profiling fallback
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
1 parent 138e3b6 commit 70b8346

7 files changed

Lines changed: 691 additions & 90 deletions

File tree

exercises/06.rerenders/01.problem.memo/tests/memoized.test.ts

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,72 @@ test('Only ListItem should not rerender when clicking force rerender', async ({
1212
const scriptToInject = `
1313
<script>
1414
let internals
15+
const PerformedWorkFlag = 0b000000000000000000000000001
16+
const UserCodeFiberTags = new Set([0, 1, 9, 11, 14, 15])
17+
18+
let activeFallbackComponentNames = null
19+
20+
function isUserCodeFiberTag(tag) {
21+
return UserCodeFiberTags.has(tag)
22+
}
23+
24+
function getDisplayNameFromType(type) {
25+
if (!type) return null
26+
27+
if (typeof type === 'function') {
28+
return type.displayName || type.name || null
29+
}
30+
31+
if (typeof type === 'object') {
32+
if (typeof type.displayName === 'string' && type.displayName) {
33+
return type.displayName
34+
}
35+
36+
if (typeof type.render === 'function') {
37+
return type.render.displayName || type.render.name || null
38+
}
39+
40+
if (type.type) {
41+
return getDisplayNameFromType(type.type)
42+
}
43+
}
44+
45+
return null
46+
}
47+
48+
function getFiberDisplayName(fiber) {
49+
return getDisplayNameFromType(fiber.type) || getDisplayNameFromType(fiber.elementType)
50+
}
51+
52+
function didFiberRender(previousFiber, nextFiber) {
53+
if (!previousFiber) return true
54+
return (nextFiber.flags & PerformedWorkFlag) === PerformedWorkFlag
55+
}
56+
57+
function collectRenderedComponentsFromCommit(root) {
58+
if (!activeFallbackComponentNames || !root?.current) return
59+
60+
const stack = [root.current]
61+
while (stack.length > 0) {
62+
const fiber = stack.pop()
63+
if (!fiber) continue
64+
65+
if (fiber.sibling) stack.push(fiber.sibling)
66+
if (fiber.child) stack.push(fiber.child)
67+
68+
if (!isUserCodeFiberTag(fiber.tag)) continue
69+
if (!didFiberRender(fiber.alternate, fiber)) continue
70+
71+
const componentName = getFiberDisplayName(fiber)
72+
if (componentName) {
73+
activeFallbackComponentNames.push(componentName)
74+
}
75+
}
76+
}
1577
1678
function enhanceExistingHook(existingHook) {
1779
const originalInject = existingHook.inject
80+
const originalCommitFiberRoot = existingHook.onCommitFiberRoot
1881
1982
existingHook.inject = (injectedInternals) => {
2083
internals = injectedInternals
@@ -23,6 +86,12 @@ test('Only ListItem should not rerender when clicking force rerender', async ({
2386
return originalInject?.call(existingHook, injectedInternals) ?? 1
2487
}
2588
89+
existingHook.onCommitFiberRoot = (...args) => {
90+
const [, root] = args
91+
collectRenderedComponentsFromCommit(root)
92+
return originalCommitFiberRoot?.apply(existingHook, args)
93+
}
94+
2695
return existingHook
2796
}
2897
@@ -34,7 +103,9 @@ test('Only ListItem should not rerender when clicking force rerender', async ({
34103
internals = injectedInternals
35104
return 1 // Returning a number as React expects a renderer ID
36105
},
37-
onCommitFiberRoot: () => {},
106+
onCommitFiberRoot: (_rendererId, root) => {
107+
collectRenderedComponentsFromCommit(root)
108+
},
38109
onCommitFiberUnmount: () => {},
39110
}
40111
}
@@ -45,19 +116,32 @@ test('Only ListItem should not rerender when clicking force rerender', async ({
45116
throw new Error('🚨 React DevTools is not available')
46117
}
47118
48-
internals.enableProfilerTimer = true
49-
internals.enableProfilerCommitHooks = true
50-
internals.injectProfilingHooks({
51-
markComponentRenderStarted: (fiber) => {
52-
componentNames.push(fiber.type.name || 'Anonymous')
53-
},
54-
})
55-
56-
await cb()
119+
const supportsInjectedProfilingHooks =
120+
typeof internals.injectProfilingHooks === 'function'
121+
122+
if (supportsInjectedProfilingHooks) {
123+
internals.enableProfilerTimer = true
124+
internals.enableProfilerCommitHooks = true
125+
internals.injectProfilingHooks({
126+
markComponentRenderStarted: (fiber) => {
127+
componentNames.push(fiber.type.name || 'Anonymous')
128+
},
129+
})
130+
} else {
131+
activeFallbackComponentNames = componentNames
132+
}
57133
58-
internals.enableProfilerTimer = false
59-
internals.enableProfilerCommitHooks = false
60-
internals.injectProfilingHooks(null)
134+
try {
135+
await cb()
136+
} finally {
137+
if (supportsInjectedProfilingHooks) {
138+
internals.enableProfilerTimer = false
139+
internals.enableProfilerCommitHooks = false
140+
internals.injectProfilingHooks(null)
141+
} else {
142+
activeFallbackComponentNames = null
143+
}
144+
}
61145
62146
return componentNames
63147
}

exercises/06.rerenders/01.solution.memo/tests/memoized.test.ts

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,72 @@ test('Only ListItem should not rerender when clicking force rerender', async ({
1212
const scriptToInject = `
1313
<script>
1414
let internals
15+
const PerformedWorkFlag = 0b000000000000000000000000001
16+
const UserCodeFiberTags = new Set([0, 1, 9, 11, 14, 15])
17+
18+
let activeFallbackComponentNames = null
19+
20+
function isUserCodeFiberTag(tag) {
21+
return UserCodeFiberTags.has(tag)
22+
}
23+
24+
function getDisplayNameFromType(type) {
25+
if (!type) return null
26+
27+
if (typeof type === 'function') {
28+
return type.displayName || type.name || null
29+
}
30+
31+
if (typeof type === 'object') {
32+
if (typeof type.displayName === 'string' && type.displayName) {
33+
return type.displayName
34+
}
35+
36+
if (typeof type.render === 'function') {
37+
return type.render.displayName || type.render.name || null
38+
}
39+
40+
if (type.type) {
41+
return getDisplayNameFromType(type.type)
42+
}
43+
}
44+
45+
return null
46+
}
47+
48+
function getFiberDisplayName(fiber) {
49+
return getDisplayNameFromType(fiber.type) || getDisplayNameFromType(fiber.elementType)
50+
}
51+
52+
function didFiberRender(previousFiber, nextFiber) {
53+
if (!previousFiber) return true
54+
return (nextFiber.flags & PerformedWorkFlag) === PerformedWorkFlag
55+
}
56+
57+
function collectRenderedComponentsFromCommit(root) {
58+
if (!activeFallbackComponentNames || !root?.current) return
59+
60+
const stack = [root.current]
61+
while (stack.length > 0) {
62+
const fiber = stack.pop()
63+
if (!fiber) continue
64+
65+
if (fiber.sibling) stack.push(fiber.sibling)
66+
if (fiber.child) stack.push(fiber.child)
67+
68+
if (!isUserCodeFiberTag(fiber.tag)) continue
69+
if (!didFiberRender(fiber.alternate, fiber)) continue
70+
71+
const componentName = getFiberDisplayName(fiber)
72+
if (componentName) {
73+
activeFallbackComponentNames.push(componentName)
74+
}
75+
}
76+
}
1577
1678
function enhanceExistingHook(existingHook) {
1779
const originalInject = existingHook.inject
80+
const originalCommitFiberRoot = existingHook.onCommitFiberRoot
1881
1982
existingHook.inject = (injectedInternals) => {
2083
internals = injectedInternals
@@ -23,6 +86,12 @@ test('Only ListItem should not rerender when clicking force rerender', async ({
2386
return originalInject?.call(existingHook, injectedInternals) ?? 1
2487
}
2588
89+
existingHook.onCommitFiberRoot = (...args) => {
90+
const [, root] = args
91+
collectRenderedComponentsFromCommit(root)
92+
return originalCommitFiberRoot?.apply(existingHook, args)
93+
}
94+
2695
return existingHook
2796
}
2897
@@ -34,7 +103,9 @@ test('Only ListItem should not rerender when clicking force rerender', async ({
34103
internals = injectedInternals
35104
return 1 // Returning a number as React expects a renderer ID
36105
},
37-
onCommitFiberRoot: () => {},
106+
onCommitFiberRoot: (_rendererId, root) => {
107+
collectRenderedComponentsFromCommit(root)
108+
},
38109
onCommitFiberUnmount: () => {},
39110
}
40111
}
@@ -45,19 +116,32 @@ test('Only ListItem should not rerender when clicking force rerender', async ({
45116
throw new Error('🚨 React DevTools is not available')
46117
}
47118
48-
internals.enableProfilerTimer = true
49-
internals.enableProfilerCommitHooks = true
50-
internals.injectProfilingHooks({
51-
markComponentRenderStarted: (fiber) => {
52-
componentNames.push(fiber.type.name || 'Anonymous')
53-
},
54-
})
55-
56-
await cb()
119+
const supportsInjectedProfilingHooks =
120+
typeof internals.injectProfilingHooks === 'function'
121+
122+
if (supportsInjectedProfilingHooks) {
123+
internals.enableProfilerTimer = true
124+
internals.enableProfilerCommitHooks = true
125+
internals.injectProfilingHooks({
126+
markComponentRenderStarted: (fiber) => {
127+
componentNames.push(fiber.type.name || 'Anonymous')
128+
},
129+
})
130+
} else {
131+
activeFallbackComponentNames = componentNames
132+
}
57133
58-
internals.enableProfilerTimer = false
59-
internals.enableProfilerCommitHooks = false
60-
internals.injectProfilingHooks(null)
134+
try {
135+
await cb()
136+
} finally {
137+
if (supportsInjectedProfilingHooks) {
138+
internals.enableProfilerTimer = false
139+
internals.enableProfilerCommitHooks = false
140+
internals.injectProfilingHooks(null)
141+
} else {
142+
activeFallbackComponentNames = null
143+
}
144+
}
61145
62146
return componentNames
63147
}

0 commit comments

Comments
 (0)