
React Hooks
⚛️ 18 Built-in Hooks Every React Developer Should Know
There are 18 hooks built into React. Some you'll use every day. Others you'll reach for when you need them.
This cheatsheet gives an overview of each of them.
✅ useState ✅ useReducer ✅ useActionState ✅ useOptimistic ✅ useEffect ✅ useLayoutEffect ✅ useInsertionEffect ✅ useEffectEvent ✅ useMemo ✅ useCallback ✅ useDeferredValue ✅ useTransition ✅ useContext ✅ useRef ✅ useImperativeHandle ✅ useId ✅ useSyncExternalStore ✅ useDebugValue
#react #hooks #usestate #useeffect #usecontext #useref #usememo #usecallback #webdev #frontend #coding
Hooks are functions that let you tap into the power of React to manage state, side effects, references, and more.
This cheatsheet provides a quick reference to every built-in React hook.
| Hook | Description |
|---|---|
useState | Add local state to a component. |
useReducer | Manage complex state logic with a reducer function. |
useActionState | Track the pending state and result of a form action. |
useOptimistic | Show a temporary result while an async action completes. |
useEffect | Synchronize with external systems after rendering. |
useLayoutEffect | Run effects before the browser paints. |
useInsertionEffect | Insert CSS-in-JS styles before layout effects run. |
useEffectEvent | Read the latest values inside an effect without re-running it. |
useMemo | Cache the result of an expensive computation. |
| Hook | Description |
|---|---|
useCallback | Cache a function definition between renders. |
useDeferredValue | Defer updating a value to keep the UI responsive. |
useTransition | Mark state updates as non-blocking transitions. |
useContext | Read a context value without a Consumer. |
useRef | Reference a DOM node or persist a mutable value. |
useImperativeHandle | Customize the ref exposed to parent components. |
useId | Generate a unique ID for accessibility attributes. |
useSyncExternalStore | Subscribe to an external data store. |
useDebugValue | Add a label to custom hooks in React DevTools. |
useState
Add local state to a component.
const [count, setCount] = useState(0);
setCount(count + 1);
setCount((prev) => prev + 1); // functional updateFunctional updates are useful when the new state depends on the previous state.
useReducer
Manage complex state logic with a reducer function.
import { useReducer } from "react";
// Reducer function
const counterReducer = (state, action) => {
switch (action.type) {
case "INCREMENT":
return { ...state, count: state.count + 1 };
case "DECREMENT":
return { ...state, count: state.count - 1 };
case "RESET":
return { ...state, count: 0 };
default:
return state;
}
};// continued...
// Initial state
const initialState = { count: 0, lastAction: null };
function Counter() {
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div>
<h2>Count: {state.count}</h2>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button>
<button onClick={() => dispatch({ type: "RESET" })}>Reset</button>
</div>
);
}useActionState
Track a form action's result and pending state.
const [state, formAction, isPending] = useActionState(submitForm, null);
<form action={formAction}>...</form>;state will be null while the action is pending, and will update to the result when it completes.
formAction is a function that can be passed to a form's action prop to handle the submission.
isPending is a boolean that indicates whether the action is currently in progress.
useOptimistic
Enables optimistic UI updates, showing the expected result immediately while the actual async operation completes in the background.
import { useOptimistic, useState } from "react";
function TodoList() {
const [todos, setTodos] = useState([]);
// Optimistic state - updates immediately, then syncs with real state
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(current, newTodo) => [...current, newTodo],
);
const [input, setInput] = useState("");// continued...
const handleSubmit = async (e) => {
e.preventDefault();
if (!input.trim()) return;
const newTodo = { id: Date.now(), text: input, completed: false };
// UI updates instantly
addOptimisticTodo(newTodo);
setInput("");
// Simulate API call
await new Promise((resolve) => setTimeout(resolve, 1000));
// Real state updates after API completes
setTodos((prev) => [...prev, newTodo]);
};// continued...
return (
<div>
<h2>Todo List</h2>
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Add todo..."
/>
<button type="submit">Add</button>
</form>
<ul>
{optimisticTodos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}When the submit button is clicked, the new todo appears immediately in the list (optimistic update), and after the API call, the real state is updated to reflect the change.
useEffect
Synchronize with external systems after rendering, such as subscriptions, timers, or manually changing the DOM.
useEffect(() => {
const subscription = subscribe();
return () => subscription.unsubscribe(); // cleanup
}, []);useLayoutEffect
Runs synchronously after React updates the DOM but before the browser paints to the screen. It's perfect for DOM measurements and mutations that need to happen before the user sees the update.
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
}, []);useInsertionEffect
Insert CSS-in-JS styles before layout effects run. For styling libraries only.
useInsertionEffect(() => {
// insert <style> tag
}, []);useEffectEvent
Read the latest props and states inside an effect without causing the effect to re-run when those values change.
const onMessage = useEffectEvent((msg) => {
showNotification(msg, theme); // always the latest theme
});
useEffect(() => {
chat.on("message", onMessage);
}, []); // does not re-run when theme changesuseMemo
Cache the result of an expensive computation. No longer necessary if you're using the React Compiler.
const total = useMemo(
() => items.reduce((sum, item) => sum + item.price, 0),
[items],
);useCallback
Cache a function definition between renders. No longer necessary if you're using the React Compiler.
const increment = useCallback(() => setCount((c) => c + 1), []);useDeferredValue
Defer updating a value to keep the UI responsive.
import { useState, useDeferredValue, useMemo } from "react";
function SearchApp() {
const [query, setQuery] = useState("");
const deferredQuery = useDeferredValue(query);
// Expensive filtering - only runs when deferredQuery changes
const filteredItems = () => {
const items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`,
}));
if (!deferredQuery) return items;
return items.filter((item) =>
item.name.toLowerCase().includes(deferredQuery.toLowerCase()),
);
};// continued...
return (
<div>
<h2>Search</h2>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search items..."
style={{ width: "100%", padding: "10px" }}
/>
<p>Found: {filteredItems.length} items</p>
<div style={{ maxHeight: "300px", overflowY: "auto" }}>
{filteredItems.slice(0, 20).map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
</div>
);
}During the initial render, deferredQuery will be the same as query.
As the user types in the search box, deferredQuery will lag behind query, allowing the UI to remain responsive while the expensive filtering operation runs.
useTransition
Mark state updates as non-blocking transitions.
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState("about");
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...useContext
Read a context value, eliminating prop drilling and providing a clean way to share data across components.
import { useState, useContext, createContext } from "react";
// 1. Create context
const ThemeContext = createContext();
// 2. Provider component
function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggleTheme = () => {
setTheme((prev) => (prev === "light" ? "dark" : "light"));
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}// continued...
// 3. Consumer component using useContext
function ThemedButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
onClick={toggleTheme}
style={{
background: theme === "light" ? "#fff" : "#333",
color: theme === "light" ? "#333" : "#fff",
padding: "10px 20px",
border: `2px solid ${theme === "light" ? "#333" : "#fff"}`,
borderRadius: "4px",
cursor: "pointer",
}}>
Theme: {theme}
</button>
);
}// continued...
// 4. App component with Provider
function App() {
return (
<ThemeProvider>
<div style={{ padding: "20px" }}>
<h2>Theme Context</h2>
<ThemedButton />
</div>
</ThemeProvider>
);
}
export default App;useRef
Reference a DOM node or persist a mutable value without re-rendering.
import { useRef } from "react";
function App() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div style={{ padding: "20px" }}>
<input ref={inputRef} type="text" />
<button onClick={focusInput} style={{ padding: "8px 16px" }}>
Focus Input
</button>
</div>
);
}
export default App;useImperativeHandle
Customizes the instance value that is exposed when a parent component uses ref.
It's used with forwardRef to control what methods and properties are accessible to parent components.
import { useRef, useImperativeHandle, forwardRef } from "react";
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef(null);
// Expose custom methods to parent
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = "";
},
}));
return <input ref={inputRef} type="text" />;
});function App() {
const customInputRef = useRef(null);
const focusInput = () => {
customInputRef.current.focus();
};
const clearInput = () => {
customInputRef.current.clear();
};
return (
<div style={{ padding: "20px" }}>
<CustomInput ref={customInputRef} />
<button onClick={focusInput} style={{ padding: "8px 16px" }}>
Focus Input
</button>
<button onClick={clearInput} style={{ padding: "8px 16px" }}>
Clear Input
</button>
</div>
);
}
export default App;useId
Generate a unique ID for accessibility attributes.
const id = useId();
<label htmlFor={id}>Name</label>
<input id={id} />useSyncExternalStore
Subscribe to external data stores (like browser APIs, Redux, or custom stores) and get updates when they change.
const isOnline = useSyncExternalStore(subscribe, () => navigator.onLine);useDebugValue
Add a label to custom hooks in React DevTools.
function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot);
useDebugValue(isOnline ? "Online" : "Offline");
return isOnline;
}Rules of Hooks
✅ Only call hooks at the top level of a component or custom hook.
✅ Only call hooks from React functions, not regular JavaScript functions.
✅ Call hooks in the same order on every render.
Full-Stack AI Developer Roadmap
From HTML & CSS to working with AI models, all in one structured roadmap.