Side Effects and useEffect
Course: React JS & Tailwind CSS - Full Course
Introduction
Up until now, we’ve been working with state and rendering. That’s great for handling data that React knows about and can manage inside our app.
But what if we need to do something outside of React’s rendering process? Things like fetching data from an API, interacting with localStorage
, or setting up even listeners. These are called side effects.
What is a Side Effect?
A side effect is any action your code performs that affects something outside of its own scope. In React, rendering should always stay pure, meaning that given the same state and props, your component always renders the same UI.
But side effects (like fetching data or setting up even listeners) break this purity because they reach outside the component. So to address this, React provides us with a special hook: useEffect
.
What is useEffect
?
useEffect
is a React hook that lets you run side effects in your components. This is what the basic syntax for it looks like:
import { useEffect } from "react"
useEffect(() => {
// Code you want to run
}, [])
This takes two arguments:
- A function - the code you want to run (your side effect).
- A dependency array - tells React when to run this effect.
We'll use some examples to illustrate the usefulness of useEffect
.
Without useEffect
Let's take a look at what happens in React when you fetch data and store it in state without useEffect
:
const [joke, setJoke] = useState()
fetch("https://official-joke-api.appspot.com/random_joke")
.then((res) => res.json())
.then((data) => setJoke(data))
console.log(joke)
At first glance, this might seem fine - you fetch a joke, store it in state, and log it. But if you run this code, you'll notice your console quickly gets filled with jokes (before running into an error since this api only allows 100 calls per 15 minutes).
So why is this happening? The reason is because:
- React renders the component.
- The
fetch
runs immediately during rendering. - The data comes back and
setJoke(data)
is called. - Updating state causes the component to re-render.
- On re-render, the fetch function runs again… which calls
setJoke
again… which triggers another re-render… and on and on.
This causes an endless loop of renders and fetches, causing our app to slow down, and potentially even crash. To fix this, we'll need to wrap our API call in a useEffect.
Fetch With useEffect
const [joke, setJoke] = useState()
useEffect(() => {
fetch("https://official-joke-api.appspot.com/random_joke")
.then((res) => res.json())
.then((data) => setJoke(data))
}, [])
We'll leave the dependency array empty for now, so that it only runs once after rendering. We'll talk more about this later. For now let's just render the joke:
{joke ? (
<div>
<p>{joke.setup}</p>
<p>{joke.punchline}</p>
</div>
) : (
<p>Loading...</p>
)}
Note: We use a ternary because joke
is initially undefined when we use state. The fetch hasn't finished yet, so joke
will still be undefined. React will throw an error if we try to read properties of undefined
. So instead, we'll render some loading text, and once joke
is set, it'll trigger a re-render and joke
will no longer be undefined.
Dependency Array Explained
When using useEffect
, you often see the second argument: the dependency array. This array controls when your effect should run.
If the array is empty, then the effect will only run once, after the first render. Inputting a value
into the array will run the effect when the value changes.
Let's use an example to illustrate this:
import { useState, useEffect } from "react"
function App() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState("")
useEffect(() => {
setMessage(`You clicked ${count} times`)
}, [count])
return (
<div>
<p>{message}</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
)
}
Essentially, what's happening is the effect only runs when count changes. The dependency array is telling React what variable to watch (count
in this case). When we click the button, useEffect
will see that count
has changed, and so it runs the function to setMessage
, and the message updates.
Wrap-Up
useEffect
is the hook React provides for handling side effects, which are operations that happen outside of the normal rendering process, like fetching data, subscribing to events, or updating the DOM manually.
It runs after the component renders, keeping your UI responsive while performing these tasks. The dependency array controls when the effect runs - an empty array [] runs it only once, while including values [value] runs it whenever those values change.
Using useEffect
helps prevent common issues like infinite loops and ensures your components behave predictably when working with asynchronous data or external resources.
Now that you've gotten the basics of useEffect
down, you can finish building your task tracking app!