[Fast Refresh] Remount correctly when an edit changes the component kโ€ฆ
GitHub Commits - Example: React

[Fast Refresh] Remount correctly when an edit changes the component kโ€ฆ

[Fast Refresh] Remount correctly when an edit changes the component kind (#36950)

Fixes #30659.

I'm not confident in this yet but here's what Fable said based on https://github.com/react/react/pull/32214#issuecomment-2734436781:

Editing a component from a plain function to a memo or forwardRef wrapper (or between wrapper kinds) crashed with "Component is not a function", because canPreserveStateBetween only compared hook signatures. The edit was classified as state-preserving, so createWorkInProgress swapped the wrapper object in as the type of a FunctionComponent fiber and renderWithHooks tried to call it.

Two fixes, both DEV-only:

  • canPreserveStateBetween returns false when typeof or $$typeof differ. A fiber's tag is derived from the kind of its type, so state can never be preserved across a kind change; these edits must go to staleFamilies (remount). Nested wrappers need no special handling because register() creates a family per nesting level ($type/$render ids), and each level's kind is compared against its own family.

  • The reconciler now finds and remounts wrapper fibers whose outer kind changed. scheduleFibersWithFamiliesRecursively previously resolved families only through the inner implementation (type/type.render), but a kind-changing edit is recorded only on the outer type's family, so edits like memo -> function never reached the scanner and were silently dropped. The scanner now also checks the elementType's family, and the _debugNeedsRemount branch in beginWork rebuilds the fiber from family.current (the latest registered type for the fiber's identity) rather than the fiber's possibly-stale inner type.

This also fixes stale simple memo remounts recreating as plain functions, dropping the memo wrapper.

Unlike the reverted #30660 (see #32214), this does not fabricate families or guess type shapes: families are still created in exactly one place, and the same ID always resolves to the same family.

Restores the tests from #30660 and adds coverage for changing the inner type of a memo between function and forwardRef.

Co-authored-by: BIKI DAS bikid475@gmail.com
Co-authored-by: dan dan.abramov@me.com

Commit details

  • Commit: eb343c7
  • Parent: 12a4bae
  • Files changed: 4
  • Lines changed: 362 additions & 7 deletions

File tree

  • packages/react-reconciler/src/
  • packages/react-refresh/src/
  • __tests__

Diff summary

Original file line number Diff line number Diff line change
136 136
137 137
138 138
139 +
139 140
140 141
141 142
4195 4196
4196 4197
4197 4198
4198 -
4199 +
4200 +
4201 +
4202 +
4199 4203
4200 4204
4201 4205
121 121
122 122
123 123
124 +
125 +
126 +
127 +
128 +
129 +
130 +
131 +
132 +
133 +
134 +
135 +
136 +
137 +
138 +
139 +
140 +
141 +
142 +
143 +
144 +
145 +
146 +
124 147
125 148
126 149
130 153
131 154
132 155
156 +
133 157
134 158
135 159
191 215
192 216
193 217
194 -
195 -
196 -
218 +
219 +
197 220
198 221
199 222
262 285
263 286
264 287
265 -
288 +
266 289
267 290
291 +
292 +
293 +
294 +
295 +
268 296
269 297
270 -
271 298
272 299
273 300
301 +
302 +
303 +
304 +
305 +
306 +
307 +
308 +
274 309
275 310
311 +
276 312
277 313
278 314
281 317
282 318
283 319
320 +
284 321
285 322
286 323
287 324
288 -
325 +
289 326
290 327
291 328
298 335
299 336
300 337
338 +
339 +
340 +
341 +
342 +
343 +
301 344
302 345
303 346
146 146
147 147
148 148
149 +
150 +
151 +
152 +
153 +
154 +
155 +
156 +
157 +
158 +
159 +
160 +
161 +
149 162
150 163
151 164

0 commit comments

Comments

No comments yet. Start the discussion.