Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5064c7f

Browse files
authored
Revert Rerender Error Check (facebook#17519)
* Add failing test * Revert "Move rerender error check to avoid global state" This reverts commit 3e77742.
1 parent 6d105ad commit 5064c7f

File tree

2 files changed

+76
-41
lines changed

2 files changed

+76
-41
lines changed

packages/react-reconciler/src/ReactFiberHooks.js

Lines changed: 45 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ let renderPhaseUpdates: Map<
198198
UpdateQueue<any, any>,
199199
Update<any, any>,
200200
> | null = null;
201-
201+
// Counter to prevent infinite loops.
202+
let numberOfReRenders: number = 0;
202203
const RE_RENDER_LIMIT = 25;
203204

204205
// In DEV, this is the name of the currently executing primitive hook
@@ -397,6 +398,7 @@ export function renderWithHooks(
397398
398399
// didScheduleRenderPhaseUpdate = false;
399400
// renderPhaseUpdates = null;
401+
// numberOfReRenders = 0;
400402
401403
// TODO Warn if no hooks are used at all during mount, then some are used during update.
402404
// Currently we will identify the update render as a mount because memoizedState === null.
@@ -428,17 +430,8 @@ export function renderWithHooks(
428430
let children = Component(props, refOrContext);
429431
430432
if (didScheduleRenderPhaseUpdate) {
431-
// Counter to prevent infinite loops.
432-
let numberOfReRenders: number = 0;
433433
do {
434434
didScheduleRenderPhaseUpdate = false;
435-
436-
invariant(
437-
numberOfReRenders < RE_RENDER_LIMIT,
438-
'Too many re-renders. React limits the number of renders to prevent ' +
439-
'an infinite loop.',
440-
);
441-
442435
numberOfReRenders += 1;
443436
if (__DEV__) {
444437
// Even when hot reloading, allow dependencies to stabilize
@@ -465,6 +458,7 @@ export function renderWithHooks(
465458
} while (didScheduleRenderPhaseUpdate);
466459
467460
renderPhaseUpdates = null;
461+
numberOfReRenders = 0;
468462
}
469463
470464
// We can assume the previous dispatcher is always this one, since we set it
@@ -495,6 +489,7 @@ export function renderWithHooks(
495489
// These were reset above
496490
// didScheduleRenderPhaseUpdate = false;
497491
// renderPhaseUpdates = null;
492+
// numberOfReRenders = 0;
498493
499494
invariant(
500495
!didRenderTooFewHooks,
@@ -541,6 +536,7 @@ export function resetHooks(): void {
541536
542537
didScheduleRenderPhaseUpdate = false;
543538
renderPhaseUpdates = null;
539+
numberOfReRenders = 0;
544540
}
545541
546542
function mountWorkInProgressHook(): Hook {
@@ -676,43 +672,45 @@ function updateReducer<S, I, A>(
676672
677673
queue.lastRenderedReducer = reducer;
678674
679-
if (renderPhaseUpdates !== null) {
675+
if (numberOfReRenders > 0) {
680676
// This is a re-render. Apply the new render phase updates to the previous
681677
// work-in-progress hook.
682678
const dispatch: Dispatch<A> = (queue.dispatch: any);
683-
// Render phase updates are stored in a map of queue -> linked list
684-
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
685-
if (firstRenderPhaseUpdate !== undefined) {
686-
renderPhaseUpdates.delete(queue);
687-
let newState = hook.memoizedState;
688-
let update = firstRenderPhaseUpdate;
689-
do {
690-
// Process this render phase update. We don't have to check the
691-
// priority because it will always be the same as the current
692-
// render's.
693-
const action = update.action;
694-
newState = reducer(newState, action);
695-
update = update.next;
696-
} while (update !== null);
697-
698-
// Mark that the fiber performed work, but only if the new state is
699-
// different from the current state.
700-
if (!is(newState, hook.memoizedState)) {
701-
markWorkInProgressReceivedUpdate();
702-
}
679+
if (renderPhaseUpdates !== null) {
680+
// Render phase updates are stored in a map of queue -> linked list
681+
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
682+
if (firstRenderPhaseUpdate !== undefined) {
683+
renderPhaseUpdates.delete(queue);
684+
let newState = hook.memoizedState;
685+
let update = firstRenderPhaseUpdate;
686+
do {
687+
// Process this render phase update. We don't have to check the
688+
// priority because it will always be the same as the current
689+
// render's.
690+
const action = update.action;
691+
newState = reducer(newState, action);
692+
update = update.next;
693+
} while (update !== null);
703694
704-
hook.memoizedState = newState;
705-
// Don't persist the state accumulated from the render phase updates to
706-
// the base state unless the queue is empty.
707-
// TODO: Not sure if this is the desired semantics, but it's what we
708-
// do for gDSFP. I can't remember why.
709-
if (hook.baseUpdate === queue.last) {
710-
hook.baseState = newState;
711-
}
695+
// Mark that the fiber performed work, but only if the new state is
696+
// different from the current state.
697+
if (!is(newState, hook.memoizedState)) {
698+
markWorkInProgressReceivedUpdate();
699+
}
700+
701+
hook.memoizedState = newState;
702+
// Don't persist the state accumulated from the render phase updates to
703+
// the base state unless the queue is empty.
704+
// TODO: Not sure if this is the desired semantics, but it's what we
705+
// do for gDSFP. I can't remember why.
706+
if (hook.baseUpdate === queue.last) {
707+
hook.baseState = newState;
708+
}
712709
713-
queue.lastRenderedState = newState;
710+
queue.lastRenderedState = newState;
714711
715-
return [newState, dispatch];
712+
return [newState, dispatch];
713+
}
716714
}
717715
return [hook.memoizedState, dispatch];
718716
}
@@ -1205,6 +1203,12 @@ function dispatchAction<S, A>(
12051203
queue: UpdateQueue<S, A>,
12061204
action: A,
12071205
) {
1206+
invariant(
1207+
numberOfReRenders < RE_RENDER_LIMIT,
1208+
'Too many re-renders. React limits the number of renders to prevent ' +
1209+
'an infinite loop.',
1210+
);
1211+
12081212
if (__DEV__) {
12091213
warning(
12101214
typeof arguments[3] !== 'function',

packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2493,4 +2493,35 @@ describe('ReactHooksWithNoopRenderer', () => {
24932493
expect(Scheduler).toHaveYielded(['Step: 5, Shadow: 5']);
24942494
expect(ReactNoop).toMatchRenderedOutput('5');
24952495
});
2496+
2497+
it('should process the rest pending updates after a render phase update', () => {
2498+
// Similar to previous test, except using a preceding render phase update
2499+
// instead of new props.
2500+
let updateA;
2501+
let updateC;
2502+
function App() {
2503+
const [a, setA] = useState(false);
2504+
const [b, setB] = useState(false);
2505+
if (a !== b) {
2506+
setB(a);
2507+
}
2508+
// Even though we called setB above,
2509+
// we should still apply the changes to C,
2510+
// during this render pass.
2511+
const [c, setC] = useState(false);
2512+
updateA = setA;
2513+
updateC = setC;
2514+
return `${a ? 'A' : 'a'}${b ? 'B' : 'b'}${c ? 'C' : 'c'}`;
2515+
}
2516+
2517+
act(() => ReactNoop.render(<App />));
2518+
expect(ReactNoop).toMatchRenderedOutput('abc');
2519+
2520+
act(() => {
2521+
updateA(true);
2522+
// This update should not get dropped.
2523+
updateC(true);
2524+
});
2525+
expect(ReactNoop).toMatchRenderedOutput('ABC');
2526+
});
24962527
});

0 commit comments

Comments
 (0)