Skip to content

Commit

Permalink
Resolve defaultProps for lazy types
Browse files Browse the repository at this point in the history
  • Loading branch information
acdlite committed Aug 15, 2018
1 parent afa50d1 commit 6c75678
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 12 deletions.
119 changes: 107 additions & 12 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 190,34 @@ function updateForwardRef(
return workInProgress.child;
}

function updateForwardRefLazy(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
renderExpirationTime: ExpirationTime,
) {
const props = workInProgress.pendingProps;
const resolvedProps = resolveDefaultProps(Component, props);
if (resolvedProps !== null) {
workInProgress.pendingProps = resolvedProps;
const child = updateForwardRef(
current,
workInProgress,
Component,
renderExpirationTime,
);
workInProgress.pendingProps = workInProgress.memoizedProps = props;
return child;
} else {
return updateForwardRef(
current,
workInProgress,
Component,
renderExpirationTime,
);
}
}

function updateFragment(
current: Fiber | null,
workInProgress: Fiber,
Expand Down Expand Up @@ -286,6 314,34 @@ function updateFunctionalComponent(
return workInProgress.child;
}

function updateFunctionalComponentLazy(
current,
workInProgress,
Component,
renderExpirationTime,
) {
const props = workInProgress.pendingProps;
const resolvedProps = resolveDefaultProps(Component, props);
if (resolvedProps !== null) {
workInProgress.pendingProps = resolvedProps;
const child = updateFunctionalComponent(
current,
workInProgress,
Component,
renderExpirationTime,
);
workInProgress.pendingProps = workInProgress.memoizedProps = props;
return child;
} else {
return updateFunctionalComponent(
current,
workInProgress,
Component,
renderExpirationTime,
);
}
}

function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Expand Down Expand Up @@ -430,6 486,34 @@ function finishClassComponent(
return workInProgress.child;
}

function updateClassComponentLazy(
current,
workInProgress,
Component,
renderExpirationTime,
) {
const props = workInProgress.pendingProps;
const resolvedProps = resolveDefaultProps(Component, props);
if (resolvedProps !== null) {
workInProgress.pendingProps = resolvedProps;
const child = updateClassComponent(
current,
workInProgress,
Component,
renderExpirationTime,
);
workInProgress.pendingProps = workInProgress.memoizedProps = props;
return child;
} else {
return updateClassComponent(
current,
workInProgress,
Component,
renderExpirationTime,
);
}
}

function pushHostRootContext(workInProgress) {
const root = (workInProgress.stateNode: FiberRoot);
if (root.pendingContext) {
Expand Down Expand Up @@ -579,6 663,21 @@ function updateHostText(current, workInProgress) {
return null;
}

function resolveDefaultProps(Component, baseProps) {
if (Component && Component.defaultProps) {
// Resolve default props. Taken from ReactElement
const props = Object.assign({}, baseProps);
const defaultProps = Component.defaultProps;
for (let propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
return props;
}
return null;
}

function mountIndeterminateComponent(
current,
workInProgress,
Expand All @@ -591,6 690,7 @@ function mountIndeterminateComponent(
'likely caused by a bug in React. Please file an issue.',
);

const props = workInProgress.pendingProps;
if (
Component !== null &&
Component !== undefined &&
Expand All @@ -600,32 700,28 @@ function mountIndeterminateComponent(
if (typeof Component === 'function') {
if (shouldConstruct(Component)) {
workInProgress.tag = ClassComponentLazy;
return updateClassComponent(
return updateClassComponentLazy(
current,
workInProgress,
Component,
renderExpirationTime,
);
} else {
const child = mountIndeterminateComponent(
workInProgress.tag = FunctionalComponentLazy;
return updateFunctionalComponentLazy(
current,
workInProgress,
Component,
renderExpirationTime,
);
workInProgress.tag =
workInProgress.tag === FunctionalComponent
? FunctionalComponentLazy
: ClassComponentLazy;
return child;
}
} else if (
Component !== undefined &&
Component !== null &&
Component.$$typeof
) {
workInProgress.tag = ForwardRefLazy;
return updateForwardRef(
return updateForwardRefLazy(
current,
workInProgress,
Component,
Expand All @@ -634,7 730,6 @@ function mountIndeterminateComponent(
}
}

const props = workInProgress.pendingProps;
const unmaskedContext = getUnmaskedContext(workInProgress);
const context = getMaskedContext(workInProgress, unmaskedContext);

Expand Down Expand Up @@ -1103,7 1198,7 @@ function beginWork(
case FunctionalComponentLazy: {
const thenable = workInProgress.type;
const Component = (thenable._reactResult: any);
return updateFunctionalComponent(
return updateFunctionalComponentLazy(
current,
workInProgress,
Component,
Expand All @@ -1122,7 1217,7 @@ function beginWork(
case ClassComponentLazy: {
const thenable = workInProgress.type;
const Component = (thenable._reactResult: any);
return updateClassComponent(
return updateClassComponentLazy(
current,
workInProgress,
Component,
Expand Down Expand Up @@ -1159,7 1254,7 @@ function beginWork(
case ForwardRefLazy:
const thenable = workInProgress.type;
const Component = (thenable._reactResult: any);
return updateForwardRef(
return updateForwardRefLazy(
current,
workInProgress,
Component,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1472,6 1472,77 @@ describe('ReactSuspense', () => {
expect(ReactNoop.flush()).toEqual(['Hi again']);
expect(ReactNoop.getChildren()).toEqual([span('Hi again')]);
});

it('resolves defaultProps, on mount and update', async () => {
function T(props) {
return <Text {...props} />;
}
T.defaultProps = {text: 'Hi'};
const LazyText = Promise.resolve(T);

ReactNoop.render(
<Placeholder fallback={<Text text="Loading..." />}>
<LazyText />
</Placeholder>,
);
expect(ReactNoop.flush()).toEqual(['Loading...']);
expect(ReactNoop.getChildren()).toEqual([]);

await LazyText;

expect(ReactNoop.flush()).toEqual(['Hi']);
expect(ReactNoop.getChildren()).toEqual([span('Hi')]);

T.defaultProps = {text: 'Hi again'};

ReactNoop.render(
<Placeholder fallback={<Text text="Loading..." />}>
<LazyText text="Hi again" />
</Placeholder>,
);
expect(ReactNoop.flush()).toEqual(['Hi again']);
expect(ReactNoop.getChildren()).toEqual([span('Hi again')]);
});

it('resolves defaultProps without breaking memoization', async () => {
function LazyImpl(props) {
ReactNoop.yield('Lazy');
return (
<Fragment>
<Text text={props.siblingText} />
{props.children}
</Fragment>
);
}
LazyImpl.defaultProps = {siblingText: 'Sibling'};
const Lazy = Promise.resolve(LazyImpl);

class Stateful extends React.Component {
state = {text: 'A'};
render() {
return <Text text={this.state.text} />;
}
}

const stateful = React.createRef(null);
ReactNoop.render(
<Placeholder fallback={<Text text="Loading..." />}>
<Lazy>
<Stateful ref={stateful} />
</Lazy>
</Placeholder>,
);
expect(ReactNoop.flush()).toEqual(['Loading...']);
expect(ReactNoop.getChildren()).toEqual([]);
await Lazy;
expect(ReactNoop.flush()).toEqual(['Lazy', 'Sibling', 'A']);
expect(ReactNoop.getChildren()).toEqual([span('Sibling'), span('A')]);

// Lazy should not have re-rendered
stateful.current.setState({text: 'B'});
expect(ReactNoop.flush()).toEqual(['B']);
expect(ReactNoop.getChildren()).toEqual([span('Sibling'), span('B')]);
});
});

it('does not call lifecycles of a suspended component', async () => {
Expand Down

0 comments on commit 6c75678

Please sign in to comment.