by Alex Brown
Revisiting use of React’s Component Life Cycle Hooks in Anticipation of Async Rendering
If you’ve browsed the documentation, or kept an eye on the advice from the core React team, you’ve probably read that you shouldn’t handle subscriptions or side-effects in the
While the advice is clear, the reasoning behind these instructions hasn’t been greatly elaborated on, though not without reason. The brief explanation is that the implementation details of Fiber’s asynchronous rendering, motivating these instructions, aren’t entirely ironed out.
Because Fiber’s async rendering isn’t yet enabled, ignoring some of the wisdom regarding life cycle usage might not have bitten you, yet. In the future, this might change, and that’s what we’re going to explore in this article.
Clarification: Is Fiber ready?
If Fiber’s async rendering isn’t ready to go, you might be wondering whether the team sold you a counterfeit countdown. Rest assured, this isn’t the case. Fiber’s new engine, or more specifically the reconciliation process, has been put into operation with React v16. With that said, we can’t change gears from synchronous render to prioritised renders just yet.
How will using life cycles be impacted?
Conclusively, we don’t know until async rendering is set in stone. Otherwise the React team would have said as much. But we can draw some safe conclusions about handling subscriptions and side-effects. And that’s what we’ll explore.
For the sake of simplicity, here’s an example of subscribing to a media query list in the constructor, which presently will not cause us issues:
Before async rendering is enabled, we don’t have any issues because we can make the following guarantees about the component:
constructorwill be synchronously followed by
componentWillMount, if we opt to use it, and then
render. Importantly, we won’t be interrupted before render. Because of this, we can further guarantee…
- If the component unmounts in the future,
componentWillUnmountwill clean up the event listener (subscription) beforehand. This means that the
windowwon’t retain a reference to the component’s
handleMediaEventmethod via the media query list, therefore allowing the unmounted component to be garbage collected and hence avoid a memory leak. Failing to clean this up once wouldn’t be a big deal, but a component re-mounting and adding more listeners could cause issues over the lifetime of the app.
There is one caveat: error boundaries. I’ll touch on that in a bit.
So what changes with async rendering?
To get right to the point: many of your class component’s life cycle methods can fire more than once. This is because Fiber’s reconciliation process allows React to yield the work it is doing. Allowing the main thread to handle something that needs to be displayed urgently like animation. This can involve throwing away already completed work, potentially including invocations of the
componentDidMount are only called after React has flushed changes to its host environment. Thus avoiding these issues. Cleanup or ‘tear down’ in
componentWillUnmount should mirror the setup in
componentDidMount. Helping to ensure a failure to call this hook will not be problematic.
Thus, we need to be handling subscriptions and side-effects in
componentDidMount. Side-effects taking place in the
componentWillMount most often include network requests. They are especially troublesome to call multiple times when they result in mutations to our app’s back-end data stores.
One last note.
Like me, you might have assumed that React’s very first render is guaranteed to be always synchronous. But, this is not necessarily the case!
Brian Vaughn (who is on the core React team) informed me that the current intention is for the first render to be sync by default, with optional async being opt-in. He added that a low-priority first render might be valuable if, for example, React’s host container isn’t yet ready. Obviously, this is more applicable where your HTML body consists of more than a single
div for React to render to.
For a visual checklist of what is safe to perform and where, see Brian’s gist.
What purpose does
The use-case is very narrow. Developers often cite two desirable traits of
componentWillMount. They are:
setStatecan be called from
componentWillMount, unlike the
componentWillMountwon’t cause two renders if it occurs synchronously, before
Similarly, the reason
componentWillMount was kept in the codebase originally, as Sebastian Markbåge explains in a proposal to deprecate
componentWillMount, was to handle a side-effect that might be synchronous (if a local cache held the desired data) or asynchronous in the alternative. Today, as his demonstration code block conveys,
getInitialState, es6 class constructors and es7 property initialisers cater to this purpose.
With all this said, a read-only GET request initiated from
componentWillMount can be useful. On a slow-to-render device, for example an average mobile, it’s possible to save a few hundred milliseconds by initiating the request here rather than
componentDidMount. Of course, such a request should be idempotent/read-only as it may fire more than once.
When rendering on the server,
componentWillMountis still the only life cycle method called other than the
constructor, so it’s possible there are some use-cases there. Having not attempted server-side rendering myself, I can’t elaborate much on the topic.
So are these warnings only relevant once async rendering is live?
As Brian pointed out to me, not quite. Error boundaries, which went live with React v16, can also result in the invocation of
componentWillUpdate without a corresponding
Are there any other changes to be wary of?
React recently initiated a RFC (Request For Comment) process, enabling the wider community to discuss ideas. Two of the first RFCs are from members of the React core team, discussing significant potential changes.
- Andrew Clark submitted an RFC about changes to the context API. This hopefully will ease some of the difficulty in getting around
shouldComponentUpdatewhen attempting to broadcast state down the component tree. The RFC is here.
- Brian submitted an RFC for async-safe, static life cycle hooks. This principally involves gradually deprecating
componentWillReceieveProps. Two new static hooks are proposed:
deriveStateFromProps. You can read more from the proposal here, and the RFC here. Hopefully this article provided you with some good insight as to why these changes are proposed :).
- In the aforementioned proposal, Brian also teased a forthcoming RFC for a new SSR hook:
componentDidServerRender, taking the place of
componentWillMounton the server.
Keep in mind that these are early proposals!
About the author
A huge thank you to Brian Vaughn, of the React core team, for taking the time to read a draft of the article as well as making suggestions & corrections. In addition to working on React, Brian has authored some great open-source libraries such as React-Virtualized and JS-Search, as well as helping to answer community questions on forums like StackOverflow.