Skip to content

Call componentDidMount for children before componentDidUpdate for parent#506

Merged
robertknight merged 7 commits into
preactjs:masterfrom
robertknight:update-callback-order-v2
Apr 7, 2017
Merged

Call componentDidMount for children before componentDidUpdate for parent#506
robertknight merged 7 commits into
preactjs:masterfrom
robertknight:update-callback-order-v2

Conversation

@robertknight

Copy link
Copy Markdown
Member

Previously the componentDidUpdate hook was called immediately after a
component was updated but componentDidMount was deferred until the end
of the render cycle in order to ensure that the corresponding DOM
element was actually present in the document when the callback was
invoked.

The result was that after an update to a component which mounted new
child component instances, the parent's componentDidUpdate would be
called before the child's componentDidMount. This broke the assumptions
of the React Dev Tools and possibly other projects.

Fix the problem by flushing the queue of mount callbacks before
executing any componentDidUpdate callbacks. See the commit message for a
more detailed explainer.

Fixes #440

Previously the `componentDidUpdate` hook was called immediately after a
component was updated but `componentDidMount` was deferred until the end
of the render cycle in order to ensure that the corresponding DOM
element was actually present in the document when the callback was
invoked.

The result was that after an update to a component which mounted new
child component instances, the parent's `componentDidUpdate` would be
called before the child's `componentDidMount`. This broke the assumptions
of the React Dev Tools and possibly other projects.

Fix the problem by flushing the queue of mount callbacks before
executing any `componentDidUpdate` callbacks.

Fixes preactjs#440

----

In order for this to be "safe" (ie.  preserve the invariant that
`componentDidMount` is only called for connected [1] components), it
must be the case that all new instances mounted before an update to an
existing instance in a render cycle are connected before the update
happens.

When `componentDidUpdate` is called during a render cycle, there are two
cases to consider for components in the mount queue:

 1. The component is a descendant of the one being updated:

   A // Updated
   |- B // Mounted, pending `componentDidMount` callback

 2. The component is part of a subtree rooted at a previous sibling of
    the one being updated:

   A
   |-B // Mounted, pending `componentDidMount` callback
   |-C // Updated

Case (1) is OK because the `componentDidUpdate` callback is only called
on connected components and only after any children have been diffed and
added to the parent element, so (B) will be connected when (A) is
updated.

Case (2) is OK because the logic in `innerDiffNode` diffs each child in
order and adds it to the parent, so (B) will be connected by the time
(C) is updated.

[1] "connected" - The component's root DOM element and all of its
    children have been created and connected to the host document.
@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.004%) to 97.711% when pulling ca7509c on robertknight:update-callback-order-v2 into adf91c6 on developit:master.

@developit developit requested review from NekR, developit and mkxml January 14, 2017 00:55
@developit

Copy link
Copy Markdown
Member

Need as many eyes on this PR as we can. It passes tests, and @robertknight and I couldn't think of any reasons why it would be bad to flush early here. However, it's a major change to the deferral mechanism. Any comments, logs, inspections, etc are appreciated!

@NekR

NekR commented Jan 14, 2017

Copy link
Copy Markdown
Collaborator

I'm not sure I 100% get, but AFAIK componentDidUpdate shouldn't be called when componentDidMount is called, i.e. on initial render. Going to read commit message more carefully now though.

@robertknight

Copy link
Copy Markdown
Member Author

I'm not sure I 100% get, but AFAIK componentDidUpdate shouldn't be called
when componentDidMount is called, i.e. on initial render.

That is correct. Whenever a component is mounted, preact puts the component on a queue to trigger the componentDidMount callback. The reason for this deferral is that the callback cannot be called until the new element is actually added to the document. If a set of nested components are mounted, they can't be called until the top-most component's DOM element is inserted into the DOM.

Previously this queue was always flushed at the very end of the render cycle. However here I'm relying on the fact that given the current rendering order, it should also be safe to flush the queue whenever we get to updating an existing component.

In the original bug report, the structure was initially this:

<Router>
  <Index/>
</Router>

After a route change, the new structure looks like this:

<Router>
  <Login/>
</Router>

Previously, the componentDidMount for the <Login/> component would be fired after componentDidUpdate for the Router. It will now be fired just before. At the point when we update <Router/>, we know that any new children in this update will have been mounted and inserted into the DOM.

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.004%) to 97.699% when pulling a656247 on robertknight:update-callback-order-v2 into 76fcbee on developit:master.

@developit

Copy link
Copy Markdown
Member

Just reminding myself that this is still open. Need to verify some things and then merge.

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.004%) to 97.699% when pulling a2eca47 on robertknight:update-callback-order-v2 into 8614396 on developit:master.

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.004%) to 97.699% when pulling f691236 on robertknight:update-callback-order-v2 into bdcb6b5 on developit:master.

@developit

Copy link
Copy Markdown
Member

@robertknight I think this might fix an compat issue with React Bootstrap.

@robertknight

Copy link
Copy Markdown
Member Author

Is the React Bootstrap issue filed anywhere?

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.004%) to 97.699% when pulling 626a7e2 on robertknight:update-callback-order-v2 into 6c7e70c on developit:master.

@developit

Copy link
Copy Markdown
Member

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.004%) to 97.699% when pulling 2dbf316 on robertknight:update-callback-order-v2 into 1319ada on developit:master.

@developit

Copy link
Copy Markdown
Member

@robertknight Let's get this into the next 8 beta. Then we can just watch for reports of any lifecycle oddities.

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage remained the same at 100.0% when pulling d27386b on robertknight:update-callback-order-v2 into 9e1dd18 on developit:master.

@robertknight robertknight merged commit 468cd25 into preactjs:master Apr 7, 2017
@robertknight robertknight deleted the update-callback-order-v2 branch April 7, 2017 13:35
@ustccjw

ustccjw commented Apr 13, 2017

Copy link
Copy Markdown

Just one thing: parent's componentDidUpdate, children' componentDidMount should be called after DOM created/updated. It seems that this PR has broken it. @developit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants