webroad.online
  1. 1Web
  2. 2HTML
  3. 3CSS
  4. 4JavaScript
  5. 5TypeScript
  6. 6Git
  7. 7Tooling
  8. 8React
  9. 9State management
  10. 10Next.js
  11. 11Forms
  12. 12Data and backend
  13. 13SEO
  14. 14Tailwind CSS
  15. 15Animations
  16. 16Testing
  17. 17Architecture
React · Lesson 3 of 7

Effects and useRef

When you need useEffect, cleanup, and when you DON'T need it.

Updated

What an effect is

A component must be pure — it only computes JSX. But sometimes you need to synchronize the component with something outside React: a timer, a WebSocket connection, a browser API, a non-React library. That's what useEffect is for: code that runs after React has updated the screen.

useEffect(() => {
  const id = setInterval(tick, 1000)       // you start the synchronization
  return () => clearInterval(id)           // cleanup: you stop it
}, [])                                     // dependencies

An effect's lifecycle

  1. The component renders and appears on screen.
  2. The effect runs.
  3. When the dependencies change: React runs the old cleanup, then the effect again.
  4. On unmount: the cleanup runs.

Dependencies

The second argument The effect runs
missing after every render
[] once, after mounting
[a, b] after mounting and whenever a or b changes (compared with Object.is)

You put in the list everything the effect uses from props and state. The ESLint rule react-hooks/exhaustive-deps helps you.

You might not need an effect

The most common mistake in React: effects for things that aren't synchronization with the outside world.

Instead of an effect to... Do this
compute data from state / props compute it directly while rendering
react to a click / submit put the logic in the event handler
reset state when a prop changes key={id} on the component
fetch data from the server a Server Component or TanStack Query
tell the parent something changed call the callback in the handler that made the change

The key question: "Does this code run because the component appeared on screen, or because the user did something?" The first → an effect. The second → an event handler.

Fetching in an effect — if you really have to

useEffect(() => {
  const controller = new AbortController()
  fetch(`/api/users/${id}`, { signal: controller.signal })
    .then(r => r.json())
    .then(setUser)
    .catch(() => {})
  return () => controller.abort()      // cancels the old request if id changes quickly
}, [id])

Without the cleanup you have a race condition: the old response can arrive after the new one and overwrite it.

useRef — memory without re-rendering

useState useRef
A change triggers a render yes no
Read during rendering yes no (only in effects / handlers)
For what appears on screen DOM references, timer ids, "behind the scenes" values
const inputRef = useRef<HTMLInputElement>(null)
<input ref={inputRef} />
inputRef.current?.focus()

Strict Mode

In development, React mounts → unmounts → mounts again every component, to check that effects have a correct cleanup. If something "runs twice" and breaks, the effect is missing its cleanup. In production it runs only once.

Summary

  • An effect = synchronization with something outside React; cleanup is mandatory when you start something.
  • The dependencies = everything the effect uses.
  • Calculations → during rendering; reactions to actions → handlers; data → Server Components / TanStack Query.

Official sources

Exercises

Was this page helpful?

One tap — no account needed.