How many hooks are in React 19?
A Complete List of Hooks Available in React 19
There are a total of 19 hooks, as listed below:
- State Management Hooks
useState
useReducer
- Context Hook
useContext
- Reference Hooks
useRef
useImperativeHandle
- Side Effect Hooks
useEffect
useLayoutEffect
useInsertionEffect
- Performance Optimization Hooks
useMemo
useCallback
- Scheduling Hooks
useTransition
useDeferredValue
- Other Hooks
useId
useSyncExternalStore
useDebugValue
- New Hooks in React 19
use
useActionState
useFormStatus
useOptimistic
useState
useState
allow function components to have and manage their own state, enabling components to response to changes and re-render.
Here's an example of how to use it:
import React, { useState } from 'react';
export default function SimpleCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
useReducer
useReducer
is used for managing more complex state logic. It is similar to useState but is better suited for handling multiple related state values or when the next state depends on the previous state.
Here's an example of how to use it:
import React, { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
useContext
useContext
is used to share data within a component tree without having to pass props through each component layer. It allows components to directly access context defined in a parent component.
Here's an example of how to use it:
import React, { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
function DisplayTheme() {
const theme = useContext(ThemeContext);
return <div>Current theme: {theme}</div>;
}
export default function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<DisplayTheme />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</ThemeContext.Provider>
);
}
useRef
useRef
is used to create a mutable reference that persists throughout the component's lifecycle without triggering re-renders. It's commonly used to store references to DOM elements or any mutable value that doesn't require re-rendering.
Here's an example of how to use it:
import React, { useRef } from 'react';
export default function FocusInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
useImperativeHandle
useImperativeHandle
allows you to customize the instance value that is exposed to the parent component when using a ref. This Hook is typically used with forwardRef to expose custom methods or properties to the parent component.
Here's an example in TypeScript for clarity:
import React, { useRef, useImperativeHandle, forwardRef, useState } from 'react';
type CounterRef = {
increment: () => void;
getCount: () => number;
};
const CustomCounter = forwardRef<CounterRef>((props, ref) => {
const [count, setCount] = useState(0);
// The ref holds the object returned by the second argument
useImperativeHandle(ref, () => ({
increment: () => setCount(count + 1),
getCount: () => count
}));
return <div>Count: {count}</div>;
});
export default function App() {
const counterRef = useRef<CounterRef>(null);
const handleClick = () => {
if (counterRef.current) {
counterRef.current.increment();
console.log('Current count:', counterRef.current.getCount());
}
};
return (
<div>
<CustomCounter ref={counterRef} />
<button onClick={handleClick}>Increment and Log Count</button>
</div>
);
}
useEffect
useEffect
is used to perform side effects in function components, such as data fetching, subscriptions, or manually modifying the DOM. It runs after the component renders, and its execution timing can be controlled through a dependency array.
Here's an example of how to use it:
import React, { useState, useEffect } from 'react';
export default function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return <div>Timer: {count} seconds</div>;
}
useLayoutEffect
useLayoutEffect
works the same as useEffect
, with the difference in when it fires. It runs synchronously after all DOM changes but before the browser has painted. Because this process is synchronous, time-consuming operations in useLayoutEffect
can block page rendering. Generally, useEffect
should be preferred unless you need to access or modify the DOM before the paint.
Here's an example of how to use it:
import React, { useLayoutEffect } from 'react';
export default function LogDOMInfo() {
useLayoutEffect(() => {
const mainElement = document.querySelector('main');
if (mainElement) {
console.log('Main element width:', mainElement.clientWidth);
console.log('Main element height:', mainElement.clientHeight);
}
}, []);
return (
<main>
<h1>Hello, useLayoutEffect!</h1>
<p>Check the console for main element dimensions.</p>
</main>
);
}
useInsertionEffect
useInsertionEffect
is a React Hook designed specifically for CSS-in-JS libraries. It fires synchronously after DOM changes but before layout effects read the new layout. This Hook is primarily used to inject styles into the DOM before any layout shifts occur, avoiding layout jank.
Here's an example of how to use it:
import React, { useState, useInsertionEffect } from 'react';
// Simulate a simple CSS-in-JS
const injectStyle = (css: string) => {
const style = document.createElement('style');
style.textContent = css;
document.head.appendChild(style);
return style;
};
export default function DynamicStyle() {
const [color, setColor] = useState('red');
useInsertionEffect(() => {
const style = injectStyle(`
.dynamic-text {
color: ${color};
font-size: 24px;
font-weight: bold;
}
`);
return () => {
document.head.removeChild(style);
};
}, [color]);
return (
<div>
<p className="dynamic-text">This text has dynamic styling</p>
<button onClick={() => setColor(color === 'red' ? 'blue' : 'red')}>
Toggle Color
</button>
</div>
);
}
useMemo
useMemo
is used to memoize the result of a calculation. It takes a function and dependency array, and only recalculates the result when dependencies change. This is useful for optimizing values that are computationally expensive.
Here's an example of how to use it:
import React, { useState, useMemo } from 'react';
function expensiveCalculation(num: number): number {
console.log('Calculating...');
for (let i = 0; i < 1000000000; i++) {
num += 1;
}
return num;
}
export default function Calculator() {
const [num, setNum] = useState(1);
// expensiveResult will only be recalculated when num changes, if num remains the same, it won't be recalculated
const expensiveResult = useMemo(() => expensiveCalculation(num), [num]);
return (
<div>
<h2>Expensive Calculation Result: {expensiveResult}</h2>
<button onClick={() => setNum(num + 1)}>Change Calculation Input</button>
</div>
);
}
useCallback
useCallback
is used to memoize a function definition. It takes a callback and a dependency array and returns a new function only if dependenciees change. This is useful for optimizing functions passed as props to child components.
Here's an example of how to use it:
In the following example, you can see Button Button "Button 2" rendered
in console, but not Button "Button 1" rendered
. This is because useCallback's dependency array hasn't changed, so it returns the same function instead of recreating it each time.
import React, { useState, useCallback, memo } from 'react';
const ExpensiveButton = memo(
({ onClick, label }: { onClick: () => void; label: string }) => {
console.log(`Button "${label}" rendered`);
return <button onClick={onClick}>{label}</button>;
},
);
export default function CallbackExample() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const incrementCount1 = useCallback(() => {
setCount1((prevCount) => prevCount + 1);
}, []);
const incrementCount2 = () => {
setCount2((prevCount) => prevCount + 1);
};
console.log('Parent component rendered');
return (
<div>
<h2>With useCallback: {count1}</h2>
<ExpensiveButton onClick={incrementCount1} label="Button 1" />
<h2>Without useCallback: {count2}</h2>
<ExpensiveButton onClick={incrementCount2} label="Button 2" />
<p>Check the console to see which components are re-rendering.</p>
</div>
);
}
useTransition
useTransition
provides a flag to indicate the loading state, which developers can use to improve the user experience, espencially when handling slower updates that may cause the UI to lag.
Here's an example of how to use it:
import React, { useState, useTransition } from 'react';
function SlowList({ text }: { text: string }) {
const items = [];
for (let i = 0; i < 500; i++) {
items.push(<li key={i}>{text}</li>);
}
return <ul>{items}</ul>;
}
export default function TransitionExample() {
const [text, setText] = useState('');
const [deferredText, setDeferredText] = useState('');
const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setText(e.target.value);
startTransition(() => {
setDeferredText(e.target.value);
});
}
return (
<div>
<input value={text} onChange={handleChange} />
<p>Typed text: {text}</p>
{isPending ? (
<p>Updating list...</p>
) : (
<SlowList text={deferredText} />
)}
</div>
);
}
useDeferredValue
useDeferredValue
is used to delay updating a certain value. It takes a value and returns a new copy of that value, which updates more slowly. This is useful for postponing the rendering of more computationally expensive parts.
In the example below, if you type or delete content quickly, the value of deferredText will take a moment to update.
import React, { useState, useDeferredValue, memo } from 'react';
const SlowList = memo(function SlowList({ text }: { text: string }) {
console.log('Rendering SlowList');
const items = [];
for (let i = 0; i < 10000; i++) {
items.push(<li key={i}>{text}</li>);
}
return <ul>{items}</ul>;
});
export default function DeferredValueExample() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
setText(e.target.value);
}
return (
<div>
<input
value={text}
onChange={handleChange}
placeholder="Type here..."
style={{ fontSize: '18px', padding: '5px', width: '200px' }}
/>
<p>Immediate text: {text}</p>
<p style={{ color: text === deferredText ? 'black' : 'red' }}>
Deferred text: {deferredText}
</p>
<SlowList text={text} />
</div>
);
}
useId
useId
is used to generate a unique ID.
Here's an example of how to use it:
import React, { useId } from 'react';
function LabeledInput({ label }: { label: string }) {
const id = useId();
return (
<div>
<label htmlFor={id}>{label}</label>
<input id={id} type="text" />
</div>
);
}
export default function UniqueIdExample() {
return (
<div>
<h2>Form with Unique IDs</h2>
<LabeledInput label="First Name" />
<LabeledInput label="Last Name" />
<LabeledInput label="Email" />
</div>
);
}
useSyncExternalStore
useSyncExternalStore
is used to subscribe to an external data source, facilitating integration between React and external state libraries.
Here's an example of how to use it:
import React, { useSyncExternalStore } from 'react';
// Simulate an external store
const createStore = (initialState: number) => {
let state = initialState;
const listeners = new Set<() => void>();
return {
subscribe: (listener: () => void) => {
listeners.add(listener);
return () => listeners.delete(listener);
},
getSnapshot: () => state,
increment: () => {
state++;
listeners.forEach(listener => listener());
}
};
};
const store = createStore(0);
function Counter() {
const count = useSyncExternalStore(
store.subscribe,
store.getSnapshot
);
return (
<div>
<p>Count: {count}</p>
<button onClick={store.increment}>Increment</button>
</div>
);
}
export default function ExternalStoreExample() {
return (
<div>
<h2>External Store Example</h2>
<Counter />
<Counter />
</div>
);
}
useDebugValue
useDebugValue
is used to add labels for custom Hooks in React DevTools. It allows developers to provide more meaning full debug information for a custom Hook, making it easier to understand the Hook's current state when inspecting components in React DevTools.
Here's an example of how to use it:
import React, { useState, useEffect, useDebugValue } from 'react';
// Custom Hook : useOnlineStatus
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
// Using useDebugValue to add debug information
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
function StatusIndicator() {
const isOnline = useOnlineStatus();
return (
<div>
<h2>Network Status</h2>
<p>You are {isOnline ? 'online' : 'offline'}</p>
</div>
);
}
export default function DebugValueExample() {
return (
<div>
<h1>useDebugValue Example</h1>
<StatusIndicator />
</div>
);
}
use
use
is a new React Hook that works with Suppense to wait for resource loading during the redering process.
Here's an example of how to use it:
import React, { use, Suspense } from 'react';
// Simulate asynchronous data loading
const fetchComments = () =>
new Promise((resolve) =>
setTimeout(() => resolve(['Comment 1', 'Comment 2', 'Comment 3']), 1000),
);
function Comments() {
const comments = use(fetchComments());
return (
<ul>
{comments.map((comment, index) => (
<li key={index}>{comment}</li>
))}
</ul>
);
}
export default function UseExample() {
return (
<div>
<h1>Comments</h1>
<Suspense fallback={<div>Loading comments...</div>}>
<Comments />
</Suspense>
</div>
);
}
useActionState
useActionState
is used to manage the state of form submissions. Traditionally, handling form data requires manually creating multiple state, but with useActionState
, you can manage these without manual state handling.
Here's an example of how to use it:
'use client';
import React, { useActionState } from 'react';
// Simulate a server action
async function submitForm(prevState, formData) {
// Simalate network request
await new Promise((resolve) => setTimeout(resolve, 1000));
const email = formData.get('email');
if (!email.includes('@')) {
return { error: 'Invalid email address' };
}
return { success: true, message: 'Form submitted successfully' };
}
export default function ActionStateExample() {
const [state, formAction, pending] = useActionState(submitForm, {
error: null,
success: false,
message: '',
});
return (
<div>
<h1>useActionState Example</h1>
<form action={formAction}>
<div>
<label htmlFor="email">Email:</label>
<input type="email" id="email" name="email" required />
</div>
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
</form>
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
{state.success && <p style={{ color: 'green' }}>{state.message}</p>}
</div>
);
}
useFormStatus
useFormStatus
is used to retrieve the status information of the nearest ancestor <form>
element. For example, it can be utilized to adjust the style of a SubmitButton
during form submission.
Here's an example of how to use it:
import React from 'react';
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button
type="submit"
disabled={pending}
className={`px-4 py-2 rounded ${pending ? 'bg-gray-400' : 'bg-blue-500 hover:bg-blue-600'} text-white`}
>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
function handleSubmit(formData) {
// Simulate an asynchronous operation
return new Promise((resolve) => {
setTimeout(() => {
console.log('Form submitted with:', Object.fromEntries(formData));
resolve(null);
}, 2000);
});
}
export default function FormStatusExample() {
return (
<form action={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-700"
>
Name:
</label>
<input
type="text"
id="name"
name="name"
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
/>
</div>
<div>
<label
htmlFor="email"
className="block text-sm font-medium text-gray-700"
>
Email:
</label>
<input
type="email"
id="email"
name="email"
required
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
/>
</div>
<SubmitButton />
</form>
);
}
useOptimistic
useOptimistic
is used to implement optimistic updates, allowing you to immediately update the UI while waiting for an asynchronous operation to complete, providing a faster user experience.
Here's an example of how to use it:
import { useOptimistic, useState, useRef } from 'react';
async function deliverMessage(message) {
await new Promise((resolve) => setTimeout(resolve, 2000));
return message;
}
function Thread({ messages, sendMessage }) {
const formRef = useRef();
async function formAction(formData) {
addOptimisticMessage(formData.get('message'));
formRef.current.reset();
await sendMessage(formData);
}
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
messages,
(state, newMessage) => [
...state,
{
text: newMessage,
sending: true,
},
],
);
return (
<>
{optimisticMessages.map((message, index) => (
<div key={index}>
{message.text}
{!!message.sending && <small> (Sending...)</small>}
</div>
))}
<form action={formAction} ref={formRef}>
<input type="text" name="message" placeholder="Hello!" />
<button type="submit">Send</button>
</form>
</>
);
}
export default function App() {
const [messages, setMessages] = useState([
{ text: 'Hello there!', sending: false, key: 1 },
]);
async function sendMessage(formData) {
const sentMessage = await deliverMessage(formData.get('message'));
setMessages((messages) => [...messages, { text: sentMessage }]);
}
return <Thread messages={messages} sendMessage={sendMessage} />;
}