Fix resolution of outer props with React.memo() (#14312)
* Add failing test for defaultProps between lazy() and memo() * Add another regression test for defaultProps resolution order * Resolve outer props for MemoComponent
This commit is contained in:
parent
14be29b2b9
commit
0c7189d923
|
@ -250,7 +250,12 @@ function updateMemoComponent(
|
|||
): null | Fiber {
|
||||
if (current === null) {
|
||||
let type = Component.type;
|
||||
if (isSimpleFunctionComponent(type) && Component.compare === null) {
|
||||
if (
|
||||
isSimpleFunctionComponent(type) &&
|
||||
Component.compare === null &&
|
||||
// SimpleMemoComponent codepath doesn't resolve outer props either.
|
||||
Component.defaultProps === undefined
|
||||
) {
|
||||
// If this is a plain function component without default props,
|
||||
// and with only the default shallow comparison, we upgrade it
|
||||
// to a SimpleMemoComponent to allow fast path updates.
|
||||
|
@ -1739,7 +1744,9 @@ function beginWork(
|
|||
case MemoComponent: {
|
||||
const type = workInProgress.type;
|
||||
const unresolvedProps = workInProgress.pendingProps;
|
||||
const resolvedProps = resolveDefaultProps(type.type, unresolvedProps);
|
||||
// Resolve outer props first, then resolve inner props.
|
||||
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
|
||||
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
|
||||
return updateMemoComponent(
|
||||
current,
|
||||
workInProgress,
|
||||
|
|
|
@ -533,4 +533,132 @@ describe('ReactLazy', () => {
|
|||
expect(root).toMatchRenderedOutput('FooBar');
|
||||
expect(ref.current).not.toBe(null);
|
||||
});
|
||||
|
||||
// Regression test for #14310
|
||||
it('supports defaultProps defined on the memo() return value', async () => {
|
||||
const Add = React.memo(props => {
|
||||
return props.inner + props.outer;
|
||||
});
|
||||
Add.defaultProps = {
|
||||
inner: 2,
|
||||
};
|
||||
const LazyAdd = lazy(() => fakeImport(Add));
|
||||
const root = ReactTestRenderer.create(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={2} />
|
||||
</Suspense>,
|
||||
{
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
expect(root).toFlushAndYield(['Loading...']);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
// Mount
|
||||
await Promise.resolve();
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
|
||||
// Update (shallowly equal)
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={2} />
|
||||
</Suspense>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
|
||||
// Update
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={3} />
|
||||
</Suspense>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('5');
|
||||
|
||||
// Update (shallowly equal)
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={3} />
|
||||
</Suspense>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('5');
|
||||
|
||||
// Update (explicit props)
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={1} inner={1} />
|
||||
</Suspense>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('2');
|
||||
|
||||
// Update (explicit props, shallowly equal)
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={1} inner={1} />
|
||||
</Suspense>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('2');
|
||||
|
||||
// Update
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={1} />
|
||||
</Suspense>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('3');
|
||||
});
|
||||
|
||||
it('merges defaultProps in the correct order', async () => {
|
||||
let Add = React.memo(props => {
|
||||
return props.inner + props.outer;
|
||||
});
|
||||
Add.defaultProps = {
|
||||
inner: 100,
|
||||
};
|
||||
Add = React.memo(Add);
|
||||
Add.defaultProps = {
|
||||
inner: 2,
|
||||
outer: 0,
|
||||
};
|
||||
const LazyAdd = lazy(() => fakeImport(Add));
|
||||
const root = ReactTestRenderer.create(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={2} />
|
||||
</Suspense>,
|
||||
{
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
expect(root).toFlushAndYield(['Loading...']);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
// Mount
|
||||
await Promise.resolve();
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
|
||||
// Update
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd outer={3} />
|
||||
</Suspense>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('5');
|
||||
|
||||
// Update
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyAdd />
|
||||
</Suspense>,
|
||||
);
|
||||
root.unstable_flushAll();
|
||||
expect(root).toMatchRenderedOutput('2');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue