Skip to content

Commit

Permalink
[Fresh] Support re-rendering lazy() without losing state (facebook#15686
Browse files Browse the repository at this point in the history
)

* Support re-rendering lazy() without losing state

* Clearer naming
  • Loading branch information
gaearon committed Jun 19, 2019
1 parent 92bcf2a commit 7b4c2f7
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 17 deletions.
56 changes: 51 additions & 5 deletions packages/react-dom/src/__tests__/ReactFresh-test.internal.js
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ describe('ReactFresh', () => {

it('can preserve state for lazy after resolution', async () => {
if (__DEV__) {
let OuterV1 = render(() => {
let AppV1 = render(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
Expand All @@ -923,6 +923,7 @@ describe('ReactFresh', () => {
</React.Suspense>
);
}
__register__(App, 'App');

return App;
});
Expand All @@ -943,7 +944,7 @@ describe('ReactFresh', () => {
expect(el.textContent).toBe('1');

// Perform a hot update.
patch(() => {
let AppV2 = patch(() => {
function Hello() {
const [val, setVal] = React.useState(0);
return (
Expand All @@ -953,6 +954,25 @@ describe('ReactFresh', () => {
);
}
__register__(Hello, 'Hello');

const Outer = React.lazy(
() =>
new Promise(resolve => {
setTimeout(() => resolve({default: Hello}), 100);
}),
);
__register__(Outer, 'Outer');

function App() {
return (
<React.Suspense fallback={<p>Loading</p>}>
<Outer />
</React.Suspense>
);
}
__register__(App, 'App');

return App;
});

// Assert the state was preserved but color changed.
Expand All @@ -968,8 +988,12 @@ describe('ReactFresh', () => {
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');

// Perform a top-down render with a stale type.
render(() => OuterV1);
// Perform top-down renders with both fresh and stale types.
// Neither should change the state or color.
// They should always resolve to the latest version.
render(() => AppV1);
render(() => AppV2);
render(() => AppV1);
expect(container.firstChild).toBe(el);
expect(el.textContent).toBe('2');
expect(el.style.color).toBe('red');
Expand All @@ -987,7 +1011,17 @@ describe('ReactFresh', () => {
__register__(Hello, 'Hello');

// Note: no lazy wrapper this time.
return Hello;

function App() {
return (
<React.Suspense fallback={<p>Loading</p>}>
<Hello />
</React.Suspense>
);
}
__register__(App, 'App');

return App;
});

expect(container.firstChild).not.toBe(el);
Expand Down Expand Up @@ -2053,6 +2087,18 @@ describe('ReactFresh', () => {
}
});

it('can remount on signature change within a lazy simple memo wrapper', () => {
if (__DEV__) {
testRemountingWithWrapper(Hello =>
React.lazy(() => ({
then(cb) {
cb({default: React.memo(Hello)});
},
})),
);
}
});

it('can remount on signature change within forwardRef', () => {
if (__DEV__) {
testRemountingWithWrapper(Hello => React.forwardRef(Hello));
Expand Down
36 changes: 24 additions & 12 deletions packages/react-reconciler/src/ReactFiberHotReloading.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ import {
MemoComponent,
SimpleMemoComponent,
} from 'shared/ReactWorkTags';
import {REACT_FORWARD_REF_TYPE, REACT_MEMO_TYPE} from 'shared/ReactSymbols';
import {
REACT_FORWARD_REF_TYPE,
REACT_MEMO_TYPE,
REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';

type Family = {|
currentType: any,
Expand Down Expand Up @@ -108,39 +112,47 @@ export function isCompatibleFamilyForHotReloading(

const prevType = fiber.elementType;
const nextType = element.type;

// If we got here, we know types aren't === equal.
let needsCompareFamilies = false;

const $$typeofNextType =
typeof nextType === 'object' && nextType !== null
? nextType.$$typeof
: null;

switch (fiber.tag) {
case FunctionComponent: {
if (typeof nextType === 'function') {
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
// We don't know the inner type yet.
// We're going to assume that the lazy inner type is stable,
// and so it is sufficient to avoid reconciling it away.
// We're not going to unwrap or actually use the new lazy type.
needsCompareFamilies = true;
}
break;
}
case ForwardRef: {
if (
typeof nextType === 'object' &&
nextType !== null &&
nextType.$$typeof === REACT_FORWARD_REF_TYPE
) {
if ($$typeofNextType === REACT_FORWARD_REF_TYPE) {
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
needsCompareFamilies = true;
}
break;
}
case MemoComponent:
case SimpleMemoComponent: {
if (
typeof nextType === 'object' &&
nextType !== null &&
nextType.$$typeof === REACT_MEMO_TYPE
) {
if ($$typeofNextType === REACT_MEMO_TYPE) {
// TODO: if it was but can no longer be simple,
// we shouldn't set this.
needsCompareFamilies = true;
} else if ($$typeofNextType === REACT_LAZY_TYPE) {
needsCompareFamilies = true;
}
break;
}
// TODO: maybe support lazy?
default:
return false;
}
Expand Down

0 comments on commit 7b4c2f7

Please sign in to comment.