Skip to content

Commit

Permalink
Merge pull request #2796 from preactjs/suspense-reset-hoooks
Browse files Browse the repository at this point in the history
  • Loading branch information
marvinhagemeister committed Oct 12, 2020
2 parents c7a7253 + a679795 commit 26b93cd
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 1 deletion.
9 changes: 9 additions & 0 deletions compat/src/suspense.js
Expand Up @@ -24,10 +24,19 @@ options._catchError = function(error, newVNode, oldVNode) {

function detachedClone(vnode) {
if (vnode) {
if (vnode._component && vnode._component.__hooks) {
vnode._component.__hooks._list.forEach(effect => {
if (typeof effect._cleanup == 'function') effect._cleanup();
});

vnode._component.__hooks = null;
}

vnode = assign({}, vnode);
vnode._component = null;
vnode._children = vnode._children && vnode._children.map(detachedClone);
}

return vnode;
}

Expand Down
121 changes: 120 additions & 1 deletion compat/test/browser/suspense.test.js
Expand Up @@ -6,7 +6,10 @@ import React, {
Suspense,
lazy,
Fragment,
createContext
createContext,
useState,
useEffect,
useLayoutEffect
} from 'preact/compat';
import { setupScratch, teardown } from '../../../test/_util/helpers';

Expand Down Expand Up @@ -161,6 +164,122 @@ describe('suspense', () => {
});
});

it('should reset hooks of components', () => {
let set;
const LazyComp = ({ name }) => <div>Hello from {name}</div>;

/** @type {() => Promise<void>} */
let resolve;
const Lazy = lazy(() => {
const p = new Promise(res => {
resolve = () => {
res({ default: LazyComp });
return p;
};
});

return p;
});

const Parent = ({ children }) => {
const [state, setState] = useState(false);
set = setState;

return (
<div>
<p>hi</p>
{state && children}
</div>
);
};

render(
<Suspense fallback={<div>Suspended...</div>}>
<Parent>
<Lazy name="LazyComp" />
</Parent>
</Suspense>,
scratch
);
expect(scratch.innerHTML).to.eql(`<div><p>hi</p></div>`);

set(true);
rerender();

expect(scratch.innerHTML).to.eql('<div>Suspended...</div>');

return resolve().then(() => {
rerender();
expect(scratch.innerHTML).to.eql(`<div><p>hi</p></div>`);
});
});

it('should call effect cleanups', () => {
let set;
const effectSpy = sinon.spy();
const layoutEffectSpy = sinon.spy();
const LazyComp = ({ name }) => <div>Hello from {name}</div>;

/** @type {() => Promise<void>} */
let resolve;
const Lazy = lazy(() => {
const p = new Promise(res => {
resolve = () => {
res({ default: LazyComp });
return p;
};
});

return p;
});

const Parent = ({ children }) => {
const [state, setState] = useState(false);
set = setState;
useEffect(() => {
return () => {
effectSpy();
};
}, []);

useLayoutEffect(() => {
return () => {
layoutEffectSpy();
};
}, []);

return state ? (
<div>{children}</div>
) : (
<div>
<p>hi</p>
</div>
);
};

render(
<Suspense fallback={<div>Suspended...</div>}>
<Parent>
<Lazy name="LazyComp" />
</Parent>
</Suspense>,
scratch
);

set(true);
rerender();
expect(scratch.innerHTML).to.eql('<div>Suspended...</div>');
expect(effectSpy).to.be.calledOnce;
expect(layoutEffectSpy).to.be.calledOnce;

return resolve().then(() => {
rerender();
expect(effectSpy).to.be.calledOnce;
expect(layoutEffectSpy).to.be.calledOnce;
expect(scratch.innerHTML).to.eql(`<div><p>hi</p></div>`);
});
});

it('should support a call to setState before rendering the fallback', () => {
const LazyComp = ({ name }) => <div>Hello from {name}</div>;

Expand Down
1 change: 1 addition & 0 deletions mangle.json
Expand Up @@ -25,6 +25,7 @@
"props": {
"cname": 6,
"props": {
"$_cleanup": "__c",
"$_afterPaintQueued": "__a",
"$__hooks": "__H",
"$_list": "__",
Expand Down

0 comments on commit 26b93cd

Please sign in to comment.