Skip to content

Commit 0f2f90b

Browse files
authored
getDerivedStateFrom{Props,Catch} should update updateQueue.baseState (#12528)
Based on a bug found in UFI2. There have been several bugs related to the update queue (and specifically baseState) recently, so I'm going to follow-up with some refactoring to clean it up. This is a quick fix so we can ship a patch release.
1 parent da4e855 commit 0f2f90b

File tree

2 files changed

+92
-0
lines changed

2 files changed

+92
-0
lines changed

packages/react-reconciler/src/ReactFiberClassComponent.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,17 @@ export default function(
839839
newState === null || newState === undefined
840840
? derivedStateFromProps
841841
: Object.assign({}, newState, derivedStateFromProps);
842+
843+
// Update the base state of the update queue.
844+
// FIXME: This is getting ridiculous. Refactor plz!
845+
const updateQueue = workInProgress.updateQueue;
846+
if (updateQueue !== null) {
847+
updateQueue.baseState = Object.assign(
848+
{},
849+
updateQueue.baseState,
850+
derivedStateFromProps,
851+
);
852+
}
842853
}
843854
if (derivedStateFromCatch !== null && derivedStateFromCatch !== undefined) {
844855
// Render-phase updates (like this) should not be added to the update queue,
@@ -848,6 +859,17 @@ export default function(
848859
newState === null || newState === undefined
849860
? derivedStateFromCatch
850861
: Object.assign({}, newState, derivedStateFromCatch);
862+
863+
// Update the base state of the update queue.
864+
// FIXME: This is getting ridiculous. Refactor plz!
865+
const updateQueue = workInProgress.updateQueue;
866+
if (updateQueue !== null) {
867+
updateQueue.baseState = Object.assign(
868+
{},
869+
updateQueue.baseState,
870+
derivedStateFromCatch,
871+
);
872+
}
851873
}
852874

853875
if (
@@ -1016,6 +1038,17 @@ export default function(
10161038
newState === null || newState === undefined
10171039
? derivedStateFromProps
10181040
: Object.assign({}, newState, derivedStateFromProps);
1041+
1042+
// Update the base state of the update queue.
1043+
// FIXME: This is getting ridiculous. Refactor plz!
1044+
const updateQueue = workInProgress.updateQueue;
1045+
if (updateQueue !== null) {
1046+
updateQueue.baseState = Object.assign(
1047+
{},
1048+
updateQueue.baseState,
1049+
derivedStateFromProps,
1050+
);
1051+
}
10191052
}
10201053
if (derivedStateFromCatch !== null && derivedStateFromCatch !== undefined) {
10211054
// Render-phase updates (like this) should not be added to the update queue,
@@ -1025,6 +1058,17 @@ export default function(
10251058
newState === null || newState === undefined
10261059
? derivedStateFromCatch
10271060
: Object.assign({}, newState, derivedStateFromCatch);
1061+
1062+
// Update the base state of the update queue.
1063+
// FIXME: This is getting ridiculous. Refactor plz!
1064+
const updateQueue = workInProgress.updateQueue;
1065+
if (updateQueue !== null) {
1066+
updateQueue.baseState = Object.assign(
1067+
{},
1068+
updateQueue.baseState,
1069+
derivedStateFromCatch,
1070+
);
1071+
}
10281072
}
10291073

10301074
if (

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,4 +373,52 @@ describe('ReactIncrementalUpdates', () => {
373373
});
374374
ReactNoop.flush();
375375
});
376+
377+
it('getDerivedStateFromProps should update base state of updateQueue (based on product bug)', () => {
378+
// Based on real-world bug.
379+
380+
let foo;
381+
class Foo extends React.Component {
382+
state = {value: 'initial state'};
383+
static getDerivedStateFromProps() {
384+
return {value: 'derived state'};
385+
}
386+
render() {
387+
foo = this;
388+
return (
389+
<React.Fragment>
390+
<span prop={this.state.value} />
391+
<Bar />
392+
</React.Fragment>
393+
);
394+
}
395+
}
396+
397+
let bar;
398+
class Bar extends React.Component {
399+
render() {
400+
bar = this;
401+
return null;
402+
}
403+
}
404+
405+
ReactNoop.flushSync(() => {
406+
ReactNoop.render(<Foo />);
407+
});
408+
expect(ReactNoop.getChildren()).toEqual([span('derived state')]);
409+
410+
ReactNoop.flushSync(() => {
411+
// Triggers getDerivedStateFromProps again
412+
ReactNoop.render(<Foo />);
413+
// The noop callback is needed to trigger the specific internal path that
414+
// led to this bug. Removing it causes it to "accidentally" work.
415+
foo.setState({value: 'update state'}, function noop() {});
416+
});
417+
expect(ReactNoop.getChildren()).toEqual([span('derived state')]);
418+
419+
ReactNoop.flushSync(() => {
420+
bar.setState({});
421+
});
422+
expect(ReactNoop.getChildren()).toEqual([span('derived state')]);
423+
});
376424
});

0 commit comments

Comments
 (0)