diff --git a/compat/test/browser/useSyncExternalStore.test.js b/compat/test/browser/useSyncExternalStore.test.js index 2f5ab16a03..c51760cd24 100644 --- a/compat/test/browser/useSyncExternalStore.test.js +++ b/compat/test/browser/useSyncExternalStore.test.js @@ -658,10 +658,7 @@ describe('useSyncExternalStore', () => { await act(() => { store.set(1); }); - // Preact logs differ from React here cuz of how we do rerendering. We - // rerender subtrees and then commit effects so Child2 never sees the - // update to 1 cuz Child1 rerenders and runs its layout effects first. - assertLog([1, /*1,*/ 'Reset back to 0', 0, 0]); + assertLog([1, 1, 'Reset back to 0', 0, 0]); expect(container.textContent).to.equal('00'); }); diff --git a/src/component.js b/src/component.js index 48520bb375..a5e7fd513f 100644 --- a/src/component.js +++ b/src/component.js @@ -2,7 +2,7 @@ import { assign } from './util'; import { diff, commitRoot } from './diff/index'; import options from './options'; import { Fragment } from './create-element'; -import { MODE_HYDRATE } from './constants'; +import { EMPTY_ARR, MODE_HYDRATE } from './constants'; /** * Base Component class. Provides `setState()` and `forceUpdate()`, which @@ -120,12 +120,10 @@ export function getDomSibling(vnode, childIndex) { * Trigger in-place re-rendering of a component. * @param {Component} component The component to rerender */ -function renderComponent(component) { +function renderComponent(component, commitQueue, refQueue) { let oldVNode = component._vnode, oldDom = oldVNode._dom, - parentDom = component._parentDom, - commitQueue = [], - refQueue = []; + parentDom = component._parentDom; if (parentDom) { const newVNode = assign({}, oldVNode); @@ -146,11 +144,14 @@ function renderComponent(component) { ); newVNode._parent._children[newVNode._index] = newVNode; - commitRoot(commitQueue, newVNode, refQueue); + + newVNode._nextDom = undefined; if (newVNode._dom != oldDom) { updateParentDomPointers(newVNode); } + + return newVNode; } } @@ -220,21 +221,33 @@ const depthSort = (a, b) => a._vnode._depth - b._vnode._depth; /** Flush the render queue by rerendering all queued components */ function process() { let c; + let commitQueue = []; + let refQueue = []; + let root; rerenderQueue.sort(depthSort); // Don't update `renderCount` yet. Keep its value non-zero to prevent unnecessary // process() calls from getting scheduled while `queue` is still being consumed. while ((c = rerenderQueue.shift())) { if (c._dirty) { let renderQueueLength = rerenderQueue.length; - renderComponent(c); - if (rerenderQueue.length > renderQueueLength) { + root = renderComponent(c, commitQueue, refQueue) || root; + // If this WAS the last component in the queue, run commit callbacks *before* we exit the tight loop. + // This is required in order for `componentDidMount(){this.setState()}` to be batched into one flush. + // Otherwise, also run commit callbacks if the render queue was mutated. + if (renderQueueLength === 0 || rerenderQueue.length > renderQueueLength) { + commitRoot(commitQueue, root, refQueue); + refQueue.length = commitQueue.length = 0; + root = undefined; // When i.e. rerendering a provider additional new items can be injected, we want to // keep the order from top to bottom with those new items so we can handle them in a // single pass rerenderQueue.sort(depthSort); + } else if (root) { + if (options._commit) options._commit(root, EMPTY_ARR); } } } + if (root) commitRoot(commitQueue, root, refQueue); process._rerenderCount = 0; } diff --git a/src/diff/index.js b/src/diff/index.js index b5bbb49db6..16b07395a2 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -311,8 +311,6 @@ export function diff( * @param {VNode} root */ export function commitRoot(commitQueue, root, refQueue) { - root._nextDom = undefined; - for (let i = 0; i < refQueue.length; i++) { applyRef(refQueue[i], refQueue[++i], refQueue[++i]); } diff --git a/src/render.js b/src/render.js index 1ee326bc92..b550ef96af 100644 --- a/src/render.js +++ b/src/render.js @@ -59,7 +59,7 @@ export function render(vnode, parentDom, replaceNode) { refQueue ); - // Flush all queued effects + vnode._nextDom = undefined; commitRoot(commitQueue, vnode, refQueue); }