W3H in React: Hooks lifecycle flow explained in a component

How useState() and useEffect() Works - A Deep Dive into the React Hooks flow

ยท

5 min read

Context๐Ÿ‘€

During my article regarding Lazy Initialization I had presented this image provided by Donavon West and Dan Abramov:

hook-flow.png

Today, in this particular topic, we will explore the complete work flow of hooks described in the image above via our own deployed application .

Let us begin!


Phase: Mount โœจ

a. Run lazy initializers: When our component is mounting to the DOM for the very first time, as per the hooks flow diagram, React starts with running the lazy initializers. A blog post has been written regarding this step, you can absolutely check it out in detail here ๐Ÿ’—

b. Render: After that it runs the render() function present in our component code.

c. React Updates DOM: After it invokes and as it executes the render() function, it sees what all items are there in the render function. Suppose for example there are a bunch of div present inside the render() function - so React will basically 'mount' those elements under the top most level parent div and append it to the DOM.

d. Run LayoutEffects: It is basically like the useEffect() but it has a slight difference. useEffect() is asynchronous, i.e., it doesn't necessarily * blocks * your code where as useLayoutEffect() is synchronous, i.e., it can halt the browser paint/ render and actually asks the process "hey, please wait because I have some code to run" and the rendering pauses until the code present inside layout effect finishes it's execution.

e. Browser paints screen: After step d above, React temporarily stops doing it's job and it handles the control over to the browser and asks to do the painting job. At this point React has already updated the DOM - but it is not visible yet - which is why we need the "painting" process.

f. Run Effects: Once the painting job is done then it runs the "effects" - i.e., whether you are doing some sort of interaction with the web storage or whatever is written inside your useEffect().

After finishing Step f., we just wait for the user to do some sort of interaction with our React application - eventually which will initiate the "Update" phase of the hook's lifecycle.


Phase: Update ๐Ÿ”ฅ

a. Render: As described previously and in this article as well, lazy initializers are run only on the very first render so React skips that and starts with executing the contents of the render() function.

b. React Updates DOM: Next, once render() function is executed and it is ready with the necessary DOM updates, it updates the DOM.

c. Cleanup LayoutEffects: Post DOM update, it runs the clean up for the useLayoutEffect(). Basically when your component is unmounted from the DOM, the "Cleanup" gets "triggered". In simple terms, returning a function from a hook (useEffect or useLayoutEffect) is considered as a clean up function.

Probably as a React developer you have seen this error at least once in your life (if not then Kudos, you are a champ!)

Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

The message is straightforward. We're trying to change the state of a component, even after it's been unmounted.

There are multiple reasons why this can happen but the most common are that either we didnโ€™t unsubscribe to a websocket component, or the component was unmounted before an async operation finished. For example, we might want to set up a subscription to some external data source. In that case, it is important to clean up so that we donโ€™t introduce a memory leak! From Official React Docs:

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

d. Run LayoutEffects: Post Cleanup it runs the layout effects.

e. Browser Paints Screen: Browser initiates the painting process with updated DOM nodes.

f. Cleanup Effects: Clean up any side effects from the last render.

g. Run Effects: Run new effects from this render.

Ultimately the user action is finished at Step g. and the flow moves to the Unmount phase.


Phase: Unmount ๐Ÿ”š

In this phase only two Cleanups happen - Cleanup LayoutEffects and Cleanup Effects which are described in detail above.


Live Application ๐Ÿ”ฎ

The above two URLs are for the code and the deployed application.

Now if we click on the deployed app URL, we will be seeing that on the first load some random divs are getting formed ๐Ÿค”

Let us look the code of App.js closely - not so random now isn't it? Those items are actually describing the control flow of the useState() and useEffect() hooks following the diagram.

We will go through one flow in detail following the diagram which we discovered in this blog and rest of the application and it's source code is open for you to exploration!

PS, the application doesn't focus on UI so please don't curse me for that ๐Ÿ˜†

Okay, so if you are visiting the application for the first time you should see something similar to this:

APP COMPONENT
1 : App: render start

--------------------------------------------------------
2 : App: useState(() => false)

--------------------------------------------------------
3 : App: render end

--------------------------------------------------------
4 : App: useEffect(() => {})

--------------------------------------------------------
5 : App: useEffect(() => {}, [])

--------------------------------------------------------
6 : App: useEffect(() => {}, [showChild])

--------------------------------------------------------

The above scenario describes all the points from the "Mount" phase - starting from running the lazy initializers of useState() to browser painting and running effects. It stops execution after that and waits for the user, i.e., you to interact with the application!

I have captured the items getting populated in store so that we can actually see the control flow:

rhf.gif

Similarly, you can play with the child component and explore the actions via redux-devtools to understand the flow better. It has a checkbox and a counter component to play with.

N.B. The run order is for React projects with V >= 16.8.x, it slightly changes in React V17 due to this PR: github.com/facebook/react/pull/17925

A big shout out to Kent for doing awesome work for the community and inspiring millions of devs to fall in love with React, again!

Lets connect: LinkedIn Github Twitter

Other articles:

Have fun ๐Ÿ˜Ž ๐Ÿช€

ย