React Hooks Cheatsheet
Master React Hooks in React 19, including useState, useEffect, and new features like use(), useActionState, and useOptimistic for efficient component logic.
Quick Overview
React Hooks are functions that let you “hook into” React state and lifecycle features from function components. Introduced in React 16.8, they enabled developers to write entire applications with functional components, avoiding the complexities of class components. React 19 refines these patterns and introduces powerful new hooks like use(), useActionState(), and useOptimistic() to simplify asynchronous operations, form handling, and optimistic UI updates, especially with React Server Components.
- Current Stable Version: React 19
- Install: Hooks are built into React. Upgrade your project to React 19.
# For a new project (using Vite as an example) npm create vite@latest my-react-app -- --template react-ts cd my-react-app npm install react@latest react-dom@latest # Or for an existing project npm install react@latest react-dom@latest
Getting Started
To use React Hooks, you need a functional component. The simplest hook is useState, which allows you to add state to function components.
// src/App.jsx
import React, { useState } from 'react';
function Counter() {
// Declare a new state variable, which we'll call "count"
// setCount is the function to update count
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
{/* Update state using setCount */}
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default Counter;
Core Concepts
Hooks fundamentally change how you build React components by enabling state and side effects within functions.
| Concept | Description |
|---|---|
| Functional Components | Hooks allow you to use state and other React features without writing a class. Your components become plain JavaScript functions that receive props and return JSX. |
| Rules of Hooks | 1. Only call Hooks at the Top Level: Don’t call Hooks inside loops, conditions, or nested functions. 2. Only call Hooks from React Function Components: Don’t call Hooks from regular JavaScript functions. (The only other valid place is your own custom Hooks). |
| Mental Model | React runs your function components from top to bottom on every render. Hooks provide a way to “hook into” this rendering process to preserve state and perform effects across renders. |
| Custom Hooks | A JavaScript function whose name starts with “use” and that calls other Hooks. They allow you to reuse stateful logic between different components, promoting modularity and separation of concerns. |
| Dependencies Array | Used in hooks like useEffect, useCallback, and useMemo to specify values that the hook depends on. The hook will only re-run or re-compute if these dependencies change, preventing unnecessary re-renders or effects. |
Essential Hooks
Organized by common tasks.
State Management
Manage component-specific data that triggers re-renders when updated.
useState(initialState)
Adds state to functional components. Returns a stateful value and a function to update it.
import React, { useState } from 'react';
function ToggleButton() {
// Declare a boolean state, initial value is false
const [isOn, setIsOn] = useState(false);
// Toggle the state on click
const handleClick = () => setIsOn(!isOn);
return (
<button onClick={handleClick}>
{isOn ? 'ON' : 'OFF'}
</button>
);
}
useReducer(reducer, initialState)
An alternative to useState for more complex state logic or when the next state depends on the previous one.
import React, { useReducer } from 'react';
// Reducer function: defines how state changes
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
return state;
}
}
function ComplexCounter() {
// useReducer returns current state and a dispatch function
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
{/* Dispatch actions to update state */}
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
useActionState(action, initialState, permalink?) (New in React 19)
Simplifies form state management, especially with asynchronous form submissions. It handles loading states and errors automatically.
import { useActionState } from 'react';
async function updateName(previousState, formData) {
// Simulate an API call
await new Promise(resolve => setTimeout(resolve, 1000));
const newName = formData.get('name');
if (newName.length < 3) {
return { error: 'Name must be at least 3 characters' };
}
return { name: newName, error: null };
}
function NameForm() {
// useActionState manages state, dispatch function, and pending status
const [state, dispatch, isPending] = useActionState(updateName, { name: 'Guest', error: null });
return (
<form action={dispatch}>
<p>Current Name: {state.name}</p>
<input type="text" name="name" defaultValue={state.name} disabled={isPending} />
<button type="submit" disabled={isPending}>
{isPending ? 'Updating...' : 'Update Name'}
</button>
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
</form>
);
}
Side Effects & Lifecycle
Perform actions that interact with the outside world (DOM, API calls, subscriptions).
useEffect(setup, dependencies?)
Runs side effects after every render where specified dependencies have changed. Returns an optional cleanup function.
import React, { useState, useEffect } from 'react';
function DataFetcher() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Effect to fetch data when the component mounts
useEffect(() => {
async function fetchData() {
try {
const response = await fetch('https://api.example.com/items');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
fetchData();
// Optional cleanup function (e.g., to cancel ongoing requests)
return () => {
// abortController.abort(); // if using AbortController
};
}, []); // Empty dependency array means this effect runs once after the initial render
if (loading) return <p>Loading data...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}
Context & Data Consumption
Pass data deep down the component tree without prop drilling.
useContext(MyContext)
Reads the current context value. Component will re-render if the context value changes.
import React, { createContext, useContext, useState } from 'react';
// 1. Create a Context
const ThemeContext = createContext(null);
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
return (
// 2. Provide the context value
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
function ThemeToggler() {
// 3. Consume the context value
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button onClick={toggleTheme} style={{ background: theme === 'dark' ? 'black' : 'white', color: theme === 'dark' ? 'white' : 'black' }}>
Switch to {theme === 'light' ? 'Dark' : 'Light'} Mode
</button>
);
}
// Usage:
// <ThemeProvider>
// <ThemeToggler />
// </ThemeProvider>
use(promise | context) (New in React 19)
A powerful new hook to read values from resources like Promises or Context directly in components, even conditionally. It integrates seamlessly with Suspense and Error Boundaries for asynchronous data.
import React, { use, Suspense, createContext } from 'react';
// Example with a Promise
async function fetchUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
return response.json();
}
function UserProfile({ userId }) {
// Directly consume the promise, Suspense handles the loading state
const user = use(fetchUser(userId)); //
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// Example with Context
const MyContext = createContext('Default Value');
function MyComponent() {
// Directly consume context, can be used conditionally unlike useContext
const value = use(MyContext);
return <p>Context value: {value}</p>;
}
function App() {
return (
<>
<Suspense fallback={<div>Loading user...</div>}>
<UserProfile userId="123" />
</Suspense>
<MyContext.Provider value="Provided Value">
<MyComponent />
</MyContext.Provider>
</>
);
}
Performance Optimization
Memoize expensive computations or functions to prevent unnecessary re-renders.
useCallback(fn, dependencies)
Returns a memoized callback function. React will only re-create the function if one of the dependencies has changed. Useful for passing callbacks to optimized child components.
import React, { useState, useCallback } from 'react';
function Button({ onClick, children }) {
console.log('Button rendered', children);
return <button onClick={onClick}>{children}</button>;
}
function ParentComponent() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(0);
// Memoized callback: only re-creates if 'count' changes
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // Empty array means it's created once
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setOtherState(otherState + 1)}>
Update Other State ({otherState})
</button>
{/* Button component won't re-render if only otherState changes */}
<Button onClick={handleClick}>Increment Count</Button>
</div>
);
}
useMemo(factory, dependencies)
Returns a memoized value. It will only re-compute the memoized value when one of the dependencies has changed.
import React, { useState, useMemo } from 'react';
function ExpensiveCalculation({ num }) {
// Simulate an expensive calculation
const calculatedValue = useMemo(() => {
console.log('Calculating expensive value...');
let result = 0;
for (let i = 0; i < num * 1000000; i++) {
result += i;
}
return result;
}, [num]); // Re-calculate only when 'num' changes
return (
<div>
<p>Number: {num}</p>
<p>Expensive Result: {calculatedValue}</p>
</div>
);
}
function AppWithMemo() {
const [count, setCount] = useState(1);
const [multiplier, setMultiplier] = useState(1);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<button onClick={() => setMultiplier(multiplier + 1)}>Increment Multiplier</button>
<ExpensiveCalculation num={count * multiplier} />
</div>
);
}
Refs
Access DOM elements or persist mutable values across renders without causing re-renders.
useRef(initialValue)
Returns a mutable ref object whose .current property is initialized to the passed argument. The returned object will persist for the full lifetime of the component.
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
// Create a ref to store a DOM element reference
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
UI Feedback & Optimistic Updates
Enhance user experience by providing immediate feedback.
useOptimistic(state, updater) (New in React 19)
Allows you to show an immediate, “optimistic” UI update while an asynchronous background operation (e.g., a network request) completes.
import { useOptimistic } from 'react';
async function deliverMessage(message) {
// Simulate network delay
await new Promise(res => setTimeout(res, 1000));
return `Delivered: ${message}`;
}
function Chat({ messages, sendMessage }) {
// optimisticMessages initially mirrors 'messages', but updates instantly
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(currentMessages, newMessage) => [...currentMessages, { text: newMessage, sending: true }]
);
const formAction = async (formData) => {
const message = formData.get('message');
// Immediately show the message as sending
addOptimisticMessage(message);
// Send the actual message (async operation)
await deliverMessage(message);
// After delivery, the original 'messages' prop will re-render,
// and optimisticMessages will re-sync.
sendMessage(message); // Propagates to parent to update actual messages
};
return (
<form action={formAction}>
{optimisticMessages.map((msg, index) => (
<div key={index} style={{ opacity: msg.sending ? 0.5 : 1 }}>
{msg.text} {msg.sending && '(Sending...)'}
</div>
))}
<input type="text" name="message" />
<button type="submit">Send</button>
</form>
);
}
useFormStatus() (New in React 19)
Provides status information of the nearest <form> element to its nested components, without prop drilling.
import { useFormStatus } from 'react-dom'; // Note: useFormStatus is from react-dom
function SubmitButton() {
// Access form status from the nearest parent <form>
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
function UserSettingsForm() {
const handleSubmit = async (formData) => {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
alert(`Form submitted with name: ${formData.get('username')}`);
};
return (
<form action={handleSubmit}>
<label>
Username:
<input type="text" name="username" defaultValue="John Doe" />
</label>
<SubmitButton /> {/* This button automatically knows its form's pending state */}
</form>
);
}
Common Patterns
Custom Hooks for Reusability
Extracting stateful logic into reusable functions.
import { useState, useEffect } from 'react';
// Custom Hook to manage local storage
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.log(error);
return initialValue;
}
});
// Update local storage when the state changes
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(storedValue));
}, [key, storedValue]);
return [storedValue, setStoredValue];
}
function Settings() {
// Use the custom hook like a built-in one
const [username, setUsername] = useLocalStorage('username', 'Guest');
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<div>
<label>
Username:
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</label>
<p>Current Theme: {theme}</p>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</div>
);
}
Data Fetching with use() and Suspense (React 19)
Modern approach for async data fetching, especially in Server Components.
import React, { use, Suspense } from 'react';
async function fetchProducts() {
// In a real app, this might be a server-side fetch
const response = await fetch('https://api.example.com/products');
return response.json();
}
function ProductList() {
// `use` hook makes data fetching synchronous-looking
const products = use(fetchProducts());
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name} - ${product.price}</li>
))}
</ul>
);
}
function AppWithProducts() {
return (
<Suspense fallback={<div>Loading products...</div>}>
<ProductList />
</Suspense>
);
}
Gotchas & Tips
- Don’t Break the Rules of Hooks: Always call hooks at the top level of your component and only from React function components or custom hooks. Violating this leads to subtle bugs and unpredictable behavior.
- Stale Closures: Be mindful of closures, especially in
useEffect. If your effect uses variables from the component’s scope, they should be in the dependency array. Forgetting them can lead to the effect “seeing” outdated values.// BAD: counter might be stale useEffect(() => { const id = setInterval(() => { // 'count' here might be the initial 0 if not in dependencies console.log(count); }, 1000); return () => clearInterval(id); }, []); // Missing 'count' // GOOD: 'count' is up-to-date, or use functional update useEffect(() => { const id = setInterval(() => { setCount(prevCount => prevCount + 1); // Functional update avoids 'count' dependency }, 1000); return () => clearInterval(id); }, []); // Correct as setCount from useState is stable - Dependency Array Completeness: Ensure your
useEffect,useCallback,useMemodependency arrays include all values from the component scope that the hook uses. If the linter suggests it, include it. - Infinite
useEffectLoops: Setting state insideuseEffectwithout proper dependencies can cause an infinite re-render loop. For example, ifuseEffectsets state, that state change causes a re-render, which re-runsuseEffect, and so on.// BAD useEffect(() => { setCount(count + 1); // This will cause an infinite loop }, [count]); // 'count' changes, effect runs, changes 'count' again - State Updates are Asynchronous:
useState’s setter function (setCount) does not immediately update the state value (count). If you need to perform an action after state has definitely updated, useuseEffector the functional update form of the setter. - Avoid Over-Memoizing: Don’t wrap every function or value in
useCallbackoruseMemo. Memoization has a cost. Use it when you observe performance issues or when passing props toReact.memowrapped child components. - Cleanup Functions in
useEffect: If youruseEffectsubscribes to something (like an event listener, a timer, or an external data source), return a cleanup function to unsubscribe or clear it. This prevents memory leaks. use()in Client Components: Whileuse()is powerful for promises and context, especially with Server Components, remember its current limitations and best practices. Always wrap components usinguse(Promise)in a<Suspense>boundary.
Next Steps
- Official React Docs: The definitive guide for all React concepts, including in-depth explanations of Hooks. https://react.dev/
- React
useHook RFC: For the latest on theusehook and its capabilities. https://github.com/reactjs/rfcs/blob/main/text/0213-use-hook.md - z2h.fyi/react-router-guide: Learn how to handle routing in your React applications.
- z2h.fyi/react-query-guide: Explore advanced data fetching and caching with React Query.
Source: z2h.fyi/cheatsheets/react-hooks-cheatsheet — Zero to Hero cheatsheets for developers.