diff --git a/hooks/src/index.js b/hooks/src/index.js index 6af5611123..57cfcbc50d 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -331,7 +331,11 @@ function afterPaint(newQueueLength) { * @param {import('./internal').EffectHookState} hook */ function invokeCleanup(hook) { + // A hook cleanup can introduce a call to render which creates a new root, this will call options.vnode + // and move the currentComponent away. + const comp = currentComponent; if (typeof hook._cleanup == 'function') hook._cleanup(); + currentComponent = comp; } /** @@ -339,7 +343,11 @@ function invokeCleanup(hook) { * @param {import('./internal').EffectHookState} hook */ function invokeEffect(hook) { + // A hook call can introduce a call to render which creates a new root, this will call options.vnode + // and move the currentComponent away. + const comp = currentComponent; hook._cleanup = hook._value(); + currentComponent = comp; } /** diff --git a/hooks/test/browser/useEffect.test.js b/hooks/test/browser/useEffect.test.js index 4aca911e47..a16d3fc1ec 100644 --- a/hooks/test/browser/useEffect.test.js +++ b/hooks/test/browser/useEffect.test.js @@ -1,7 +1,7 @@ import { act } from 'preact/test-utils'; import { createElement, render, Fragment, Component } from 'preact'; +import { useEffect, useState, useRef } from 'preact/hooks'; import { setupScratch, teardown } from '../../../test/_util/helpers'; -import { useEffect, useState } from 'preact/hooks'; import { useEffectAssertions } from './useEffectAssertions.test'; import { scheduleEffectAssert } from '../_util/useEffectUtil'; @@ -320,4 +320,54 @@ describe('useEffect', () => { render(
Replacement
, scratch); }); + + it('support render roots from an effect', async () => { + let promise, increment; + + const Counter = () => { + const [count, setCount] = useState(0); + const renderRoot = useRef(); + useEffect(() => { + if (count > 0) { + const div = renderRoot.current; + return () => render(, div); + } + return () => 'test'; + }, [count]); + + increment = () => { + setCount(x => x + 1); + promise = new Promise(res => { + setTimeout(() => { + setCount(x => x + 1); + res(); + }); + }); + }; + + return ( +
+
Count: {count}
+
+
+ ); + }; + + const Dummy = () =>
dummy
; + + render(, scratch); + + expect(scratch.innerHTML).to.equal( + '
Count: 0
' + ); + + act(() => { + increment(); + }); + await promise; + act(() => {}); + expect(scratch.innerHTML).to.equal( + '
Count: 2
dummy
' + ); + }); });