Demystifying useEffect, why did it render?

What is useEffect?

import React, { useState, useEffect } from 'react';function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

How to prevent Infinite rendering

Let’s set up an example for a react application that displays the user object as a string, where the initial state of the user is null and we fetch the data from an external API. We want to display the user object in <pre> tags in the application or User unknown if the user is null. I’ve added two console logs to help us understand the behaviour of the hook and when the render is triggered.

import {useState, useEffect} from "react";

const App = () => {
const [user, setUser] = useState(null)
console.log('Rendering App');

useEffect(() => {
console.log('Running useEffect');
fetch('<https://jsonplaceholder.typicode.com/users/1>')
.then(response => response.json())
.then(json => setUser(json))
})

return (
<div>
{user ?
<>
<pre>{JSON.stringify(user)}</pre>
</>
:
<p>User unknown</p>
}

</div>
);
}

export default App;

Let’s have a look and see how our code gets executed.

  1. Initially, we start with the user state set to null . This is what we defined as our initial state.
  2. Next, our useEffect fetches the data from the external API and updates it using setUser then we enter a loop that we cannot get out of constantly re-rendering the application.
import {useState, useEffect} from "react";

const App = () => {
const [user, setUser] = useState(null)
console.log('Rendering App');

useEffect(() => {
console.log('Running useEffect');
fetch('<https://jsonplaceholder.typicode.com/users/1>')
.then(response => response.json())
.then(json => setUser(json))
}, [user])

return (
<div>
{user ?
<>
<pre>{JSON.stringify(user)}</pre>
</>
:
<p>User unknown</p>
}

</div>
);
}

export default App;

Terminology:

  • scalar — a single value or unit of data (e.g. integer, boolean, string)
  • compound — comprised of multiple values (e.g. array, object, set)
  • primitive — a direct value, as opposed to a reference to something that contains the real value.

Explanation

This is why our react snippet enters that endless loop. Because altho we set up the user in our useEffect to be the one we’re grabbing from the API request, that will trigger a rendering cycle where the useEffect will run again comparing the current user in our state to the one we fetch from our API. Since the objects are compared by reference, the application will enter an infinite loop.

How can we fix this?

There are multiple ways around fixing this, but so far the easiest would be stringifying the object.

import {useState, useEffect, useRef} from "react";

const App = () => {
const [user, setUser] = useState(null)
console.log('Rendering App');

useEffect(() => {
console.log('Running useEffect');
fetch('<https://jsonplaceholder.typicode.com/users/1>')
.then(response => response.json())
.then(json => {
setUser(JSON.stringify(json))
})
}, [user])

return (
<div>
{user ?
<>
<pre>{user}</pre>
</>
:
<p>User unknown</p>
}

</div>
);
}

export default App;

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store