Call them at the top level
Never call a hook inside a condition, loop or nested function. Order must be identical on every render.
Hooks let function components hold state and react to changes. Learn the two you will use every day.
Before hooks, only class components could hold state, and logic was split across lifecycle methods with names like componentDidMount. Hooks let plain functions do the same job, and they let you group related logic together instead of spreading it across a component.
When a component re-renders, its local variables are recalculated. useState gives you a value that survives those re-renders and a function to update it. Calling the updater tells React to render the component again with the new value.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Clicked {count} times
</button>
);
}
The array destructuring reads clearly: the first item is the current value and the second is the setter. Never change the value directly; always go through the setter so React knows to re-render.
Rendering should be pure, so anything that reaches outside the component, such as fetching data or subscribing to events, belongs in useEffect. React runs the effect after the render completes.
import { useEffect, useState } from "react";
function UserName({ id }) {
const [name, setName] = useState("");
useEffect(() => {
let active = true;
fetch(`/api/users/${id}`)
.then((response) => response.json())
.then((user) => {
if (active) setName(user.name);
});
return () => {
active = false;
};
}, [id]);
return <p>{name}</p>;
}
The dependency array decides when the effect re-runs. Listing [id] means it runs once on mount and again whenever id changes. An empty array runs it once, and omitting the array runs it after every render.
Some effects start something that must be stopped: a timer, a subscription or an in-flight request. Return a function from the effect to clean up, and React calls it before the next run and when the component unmounts.
The active flag above is a simple version of this. It prevents an old request from writing state after the component has moved on, which is a common source of confusing bugs.
Never call a hook inside a condition, loop or nested function. Order must be identical on every render.
Call hooks from components or from your own custom hooks, never from an ordinary helper function.
Starting a function name with use lets React and linters treat it as a hook.
React relies on the order of hook calls to match state with a component. Branches that sometimes call a hook break that order, which is why the rules exist.
When the same logic appears in several components, extract it into a custom hook. It is just a function whose name starts with use and that may call other hooks.
import { useEffect, useState } from "react";
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const onResize = () => setWidth(window.innerWidth);
window.addEventListener("resize", onResize);
return () => window.removeEventListener("resize", onResize);
}, []);
return width;
}
Any component can now call useWindowWidth() and receive the current width. The component stays focused on rendering, and the reusable behaviour lives in one tested place.
These two hooks cache a value or a function between renders so that unchanged dependencies do not trigger extra work. Use them when a calculation is genuinely expensive or when a stable reference is required by a memoised child. Reaching for them everywhere adds complexity without a real gain.