React useEffect and Other Hooks
The useEffect hook lets a component do work outside rendering, such as loading data, starting a timer or changing the page title.
What is a Side Effect?
Rendering should only calculate JSX. Anything that reaches outside the component is a side effect:
- Fetching data from a server
- Setting timers with
setTimeoutorsetInterval - Changing
document.title - Connecting to a chat or a browser API
useEffect runs your code after React has updated the screen.
Basic useEffect
Example
import { useState, useEffect } from "react";
function CartTitle() {
const [items, setItems] = useState(0);
useEffect(() => {
document.title = `Cart (${items})`;
}, [items]);
return <button onClick={() => setItems(items + 1)}>Add item</button>;
}(browser tab after 2 clicks) Cart (2)
The Dependency Array
The second argument decides when the effect runs.
| Code | When the effect runs |
|---|---|
useEffect(fn) | After every render |
useEffect(fn, []) | Only once, after the first render |
useEffect(fn, [a, b]) | After the first render and whenever a or b changes |
List every prop or state value the effect uses. If you leave one out, the effect may use an old value.
Fetching Data
Example
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function load() {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
setUsers(data);
setLoading(false);
}
load();
}, []);
if (loading) return <p>Loading...</p>;
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}The effect function itself cannot be async, so we define an async function inside it and call it.
For bigger apps, data is often loaded with a library such as TanStack Query or by a framework like Next.js. Learning useEffect first helps you understand what those tools do for you.
Cleaning Up
If an effect starts something that keeps running, return a cleanup function. React calls it before running the effect again and when the component is removed.
Example
function Clock() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const id = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(id);
}, []);
return <p>Karachi time: {time.toLocaleTimeString()}</p>;
}Without the cleanup, timers would pile up and keep running after the clock disappears.
In development, StrictMode runs your effects twice on purpose to check your cleanup works. This does not happen in the final build.
useRef: Values That Do Not Re-render
useRef gives you a box whose .current value survives renders. Changing it does not re-render. It is often used to reach a DOM element, for example to focus an input.
Example
import { useRef } from "react";
function SearchBar() {
const inputRef = useRef(null);
return (
<div>
<input ref={inputRef} placeholder="Search courses" />
<button onClick={() => inputRef.current.focus()}>Focus search</button>
</div>
);
}useContext: Sharing Data Deeply
Passing a prop through many levels is tiring. Context makes a value available to every component inside a provider.
Example
import { createContext, useContext } from "react";
const ThemeContext = createContext("light");
function Button() {
const theme = useContext(ThemeContext);
return <button className={theme}>Theme: {theme}</button>;
}
function App() {
return (
<ThemeContext value="dark">
<Button />
</ThemeContext>
);
}Theme: dark
In React 19 you can use the context itself (<ThemeContext>) as the provider. Older code writes <ThemeContext.Provider>.
Other Hooks You Will Meet
| Hook | Use it for |
|---|---|
useReducer | Complex state with many actions |
useMemo | Remembering an expensive calculation |
useCallback | Remembering a function between renders |
useId | Creating unique ids for form labels |
| Custom hooks | Your own useSomething function that reuses hook logic |
Exercise
Exercise
When does an effect with an empty dependency array [] run?
