React Render Lifecycle and useEffect Hook

Caio Borghi - May 20 '23 - - Dev Community

First of all, I want to state the obvious: React Class Components are dead 💀

This post is about how the react render lifecycle works on functional components.

Functional Components

Components are functions that must return a JSX.Element, which means HTML tags with some JS sugar.

You can declare and export a React functional component by declaring and exporting a function.

Assuming you're familiar with useState, let's look at an example:

import React, { useState } from "react"

export const Counter = () => {
  const [counterValue, setCounterValue] = useState(0)
  const increaseValue = () => {
    setCounterValue(counterValue + 1)
  }

  return (
    <button id="value-button" onClick={increaseValue}>
      { counterValue }
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

You can declare a functional component using the function keyword as well:

export function Counter() {
  const [counterValue, setCounterValue] = useState(0)
  const increaseValue = () => {
    setCounterValue(counterValue + 1)
  }

  return (
    <button id="value-button" onClick={increaseValue}>
      { counterValue }
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

The Render process

Whenever a state or prop of the component changes, the function's code will run again.

This means that each click on the 'value-button' will:

  • Increase counterValue.
  • Redeclare increaseValue, creating a new memory reference.
  • Execute the return block.

The React useEffect Hook

Suppose you want to perform an action, like showing an alert, every time a component is rendered.

You can use the useEffect hook to add side effects to your component. It has 3 different configurations: [someStateValue], [], and no dependencies.

Here's a simple example that triggers an alert whenever the component renders:

import React, { useEffect, useState } from "react"

export const Counter = () => {
  const [counterValue, setCounterValue] = useState(0)
  const increaseValue = () => {
    setCounterValue(counterValue + 1)
  }

  useEffect(() => {
    alert('I run whenever the component renders')
  }) 

  return (
    <button id="value-button" onClick={increaseValue}>
      { counterValue }
    </button>
  )
}
Enter fullscreen mode Exit fullscreen mode

In JavaScript's single-threaded environment, the Event Loop handles tasks like useEffect. The useEffect task is added to a task queue.

After the browser finishes painting the screen updates (the render), the Event Loop runs the queued tasks.

This doesn't block the rendering process.

Empty Array []

If an alert on EVERY state change sounds annoying, you can add a second parameter to useEffect, an empty array [].

This ensures that the effect runs only once after the component's initial render:

useEffect(() => {
  alert('I run once, after the component is first rendered')
}, [])
Enter fullscreen mode Exit fullscreen mode

Specific Dependencies

For specific side effects linked to one or more states, you can add them to the dependencies array.

useEffect will then execute whenever any of those states change:

useEffect(() => {
  alert('I run whenever counterValue changes.')
}, [counterValue])
Enter fullscreen mode Exit fullscreen mode

Remember, it's a good practice to include only the states used in the dependencies array.

Be mindful of the "no dependencies" scenario, as it could lead to unnecessary effect runs and performance issues.

Stay tuned for more on React rendering, where we'll delve into useCallback and useMemo hooks in future posts!

. . . . . . . . . . . . . . . . .