Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Effects: useEffect

A component should only compute markup from props and state. Everything beyond that — changing the tab title, saving data to localStorage, calling a server, starting a timer — is called a side effect. Such code goes into the useEffect hook: React runs it after updating the page.

A first effect

Let us show the number of tasks left in the tab title. In src/App.jsx:

// src/App.jsx
import { useEffect, useState } from 'react';
 
export default function App() {
  const [todos, setTodos] = useState([
    { id: 1, text: 'Buy milk', done: false },
    { id: 2, text: 'Call Oleg', done: false },
  ]);
  const left = todos.filter((todo) => !todo.done).length;
 
  useEffect(() => {
    document.title = `Tasks: ${left}`;
  });
 
  return <p>Tasks left: {left}</p>;
}

The browser tab shows "Tasks: 2". An effect without a second argument runs after every render.

The dependency array

The second argument of useEffect is the dependency array. It tells React when to run the effect again:

// src/App.jsx
useEffect(() => {
  document.title = `Tasks: ${left}`;
}, [left]);

Now the effect runs after the first render and then only when left changes. Adding a task updates the title; typing in a field does not.

FormWhen it runs
useEffect(fn)After every render
useEffect(fn, [])Once, after the first render
useEffect(fn, [a, b])After the first render and when a or b changes

Put into the array every prop and state variable used inside the effect. The linter in a Vite project warns you when something is missing.

Saving to localStorage

Another effect saves the list on every change:

// src/App.jsx
useEffect(() => {
  localStorage.setItem('todos', JSON.stringify(todos));
}, [todos]);

To load the list back when the page opens, read the initial value in useState through a function — it runs only on the first render:

// src/App.jsx
const [todos, setTodos] = useState(() => {
  const saved = localStorage.getItem('todos');
  return saved ? JSON.parse(saved) : [];
});

Add a task and reload the page — it stays in the list.

When you do not need an effect

If a value can be computed from props and state, no effect is needed — compute it during rendering. Reactions to a click belong in the event handler. An effect is only for syncing with the outside world: DOM outside React, storage, the network, timers.

Common mistake

In development, StrictMode from src/main.jsx deliberately runs every effect twice to check that it cleans up correctly. If a message from an effect appears twice in the console, it is not a bug — in the built site the effect runs once. Do not remove StrictMode to get rid of it: teach the effect to clean up after itself instead, which is the next lesson.

Thanks for reading!

Donate to the site — we work hard for you.