Custom Hook useDelayedValue example
import { useState, useEffect } from 'react';
export function useDelayedValue(value, delay) {
const [delayedValue, setDelayedValue] = useState(value);
useEffect(() => {
setTimeout(() => {
setDelayedValue(value);
}, delay);
}, [value, delay]);
return delayedValue;
}
This custom Hook returns a value that lags behind the input value by a specified number of milliseconds.
Custom Hooks reuse logic across components
You can create custom Hooks for your application's specific needs. Custom Hooks are JavaScript functions that use built-in Hooks like useState, useContext, and useEffect. They allow you to reuse stateful logic between components.
Custom Hook usePointerPosition example
import { useState, useEffect } from 'react';
export function usePointerPosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMove(e) {
setPosition({ x: e.clientX, y: e.clientY });
}
window.addEventListener('pointermove', handleMove);
return () => window.removeEventListener('pointermove', handleMove);
}, []);
return position;
}
This custom Hook tracks the cursor position by listening to pointermove events and returns the current position.
Hooks must be called at the top of components
Functions starting with `use` are called Hooks. Hooks are more restrictive than other functions. You can only call Hooks at the top of your components (or other Hooks). You cannot call Hooks inside conditions or loops. If you need to use a Hook conditionally, extract a new component and put it there.
Custom Hook for managing select options: useSelectOptions
A custom Hook can encapsulate the pattern of fetching a list and managing selection state. The useSelectOptions Hook takes a URL, fetches data when the URL changes, returns [list, selectedId, setSelectedId], and handles cleanup to prevent stale updates. Pass null as the URL to disable fetching.
Conditional URL passing to custom Hooks
When calling a custom Hook that fetches data, pass null as the URL to conditionally disable fetching. For example, pass null to useSelectOptions until the parent planetId is selected, preventing unnecessary API calls before the user has made a choice.
Extract repeated Effect logic into custom Hooks
When multiple Effects follow the same pattern (fetching data, setting a list, selecting the first item), extract the common logic into a custom Hook to reduce repetition and improve maintainability. This allows components to use the synchronization logic without directly managing the Effect.
useSelectOptions implementation example
import { useState, useEffect } from 'react';
import { fetchData } from './api.js';
export function useSelectOptions(url) {
const [list, setList] = useState(null);
const [selectedId, setSelectedId] = useState('');
useEffect(() => {
if (url === null) {
return;
}
let ignore = false;
fetchData(url).then(result => {
if (!ignore) {
setList(result);
setSelectedId(result[0].id);
}
});
return () => {
ignore = true;
}
}, [url]);
return [list, selectedId, setSelectedId];
}
This Hook manages fetching data from a URL, tracking whether the response is stale, and selecting the first item from the results. It returns the list, the selected ID, and a setter for the selected ID.
useEffectEvent import location
useEffectEvent is imported from the 'react' package: import { useEffectEvent } from 'react';
useDelayedValue implementation example
function useDelayedValue(value, delay) {
const [delayedValue, setDelayedValue] = useState(value);
useEffect(() => {
setTimeout(() => {
setDelayedValue(value);
}, delay);
}, [value, delay]);
return delayedValue;
}
useCounter hook with useInterval
The useCounter hook demonstrates reusing logic by calling another custom hook. It initializes state with useState and uses the useInterval hook to increment the count. The pattern shows how to compose custom hooks to build higher-level abstractions.
Custom Hooks must start with 'use' prefix
Hook names must start with 'use' followed by a capital letter, like useState (built-in) or useOnlineStatus (custom). This naming convention guarantees that you can always look at a component and know where its state, Effects, and other React features might hide. A function call like useOnlineStatus will most likely contain calls to other Hooks inside. React linters enforce this naming convention and will not allow you to call useState or useEffect inside functions that don't start with 'use'.
Functions that don't call Hooks should not use 'use' prefix
If your function doesn't call any Hooks, avoid the 'use' prefix. Instead, write it as a regular function without the 'use' prefix. For example, a function like getSorted that doesn't call Hooks should not be named useSorted. This ensures that your code can call this regular function anywhere, including conditions. Only give a function the 'use' prefix if it uses at least one Hook inside of it.
Custom Hooks share logic but not state
Custom Hooks let you share stateful logic, not state itself. Each call to a Hook is completely independent from every other call to the same Hook. When you call a Hook like useOnlineStatus twice in different components, each component gets its own independent state variable and Effect. If two components update together, it's because they are synchronized with the same external value (like network status), not because they share state. To share state itself between multiple components, lift it up and pass it down instead.
Custom Hooks receive latest props and state on every re-render
The code inside custom Hooks will re-run during every re-render of your component. Custom Hooks need to be pure, like components. Think of custom Hooks' code as part of your component's body. Because custom Hooks re-render together with your component, they always receive the latest props and state. This means reactive values passed to Hooks stay up-to-date.
Wrap event handlers in custom Hooks with useEffectEvent
When custom Hooks accept event handlers as parameters, wrap the event handler into a useEffectEvent to remove it from the dependencies of the Effect. This prevents the Hook from re-connecting or re-running unnecessarily every time the component re-renders and a new function instance is created. First, import useEffectEvent from React. Then create a reference to the handler using const onMessage = useEffectEvent(onReceiveMessage). Finally, use that reference in the Effect instead of the raw handler parameter, and do not include the handler in the Effect's dependency array.
Extract custom Hooks for Effects to improve code clarity
Whenever you write an Effect, consider whether it would be clearer to also wrap it in a custom Hook. Effects are an escape hatch for stepping outside React to synchronize with external systems or do something React doesn't have a built-in API for. Wrapping them into custom Hooks lets you precisely communicate your intent and how the data flows through it. This makes the calling code more declarative by constraining what it does.
Keep custom Hooks focused on concrete high-level use cases
Custom Hooks should have clear names that describe what they do at a high level. Avoid creating custom 'lifecycle' Hooks that act as alternatives to the useEffect API itself, like useMount, useEffectOnce, or useUpdateEffect. These don't fit well into the React paradigm because they don't help the linter catch missing dependencies. Instead, start by using the React API directly with Effects, then extract custom Hooks for different high-level use cases. A good custom Hook should make calling code more declarative and constrain what it does. If you struggle to pick a clear name for your Hook, it might mean that your Effect is not yet ready to be extracted.
Custom Hooks enable easier migration to new React features
Wrapping Effects in custom Hooks makes it easier to upgrade your code when React adds new features and solutions. For example, useOnlineStatus implemented with useState and useEffect can be migrated to use useSyncExternalStore, but components using the Hook don't need to change because the Hook's interface stays the same. This is why extracting common patterns into custom Hooks is beneficial: it decouples component code from implementation details, allowing updates to be made in one place.
Example: useOnlineStatus custom Hook
import { useState, useEffect } from 'react';
export function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
This custom Hook synchronizes with the browser's online and offline events and returns the current online status. Components can use it without needing to know about event listeners or cleanup logic.
Example: useFormInput custom Hook for form fields
import { useState } from 'react';
export function useFormInput(initialValue) {
const [value, setValue] = useState(initialValue);
function handleChange(e) {
setValue(e.target.value);
}
const inputProps = {
value: value,
onChange: handleChange
};
return inputProps;
}
Usage: const firstNameProps = useFormInput('Mary');
Then spread into an input: <input {...firstNameProps} />
This Hook extracts repetitive logic for managing form field state and handlers. Each call to useFormInput gets its own independent state variable.
Example: useChatRoom custom Hook with reactive parameters
import { useEffect } from 'react';
import { createConnection } from './chat.js';
import { showNotification } from './notifications.js';
export function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
connection.on('message', (msg) => {
showNotification('New message: ' + msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
This Hook accepts reactive values (serverUrl, roomId) as parameters. The Effect re-runs whenever these values change, reconnecting to the chat room with the new parameters.
Example: useChatRoom with useEffectEvent for event handlers
import { useEffect } from 'react';
import { useEffectEvent } from 'react';
import { createConnection } from './chat.js';
export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {
const onMessage = useEffectEvent(onReceiveMessage);
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
connection.on('message', (msg) => {
onMessage(msg);
});
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
By wrapping the onReceiveMessage event handler with useEffectEvent, it is removed from the dependency array. This prevents unnecessary reconnections when the component re-renders with a new function reference.
Example: Extracting custom Hook from component to reduce duplication
Before extraction, two components have duplicated logic:
function StatusBar() {
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
function handleOnline() {
setIsOnline(true);
}
function handleOffline() {
setIsOnline(false);
}
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
After extracting useOnlineStatus, both components become simple:
function StatusBar() {
const isOnline = useOnlineStatus();
return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
function SaveButton() {
const isOnline = useOnlineStatus();
return <button disabled={!isOnline}>{isOnline ? 'Save' : 'Reconnecting...'}</button>;
}
This demonstrates how custom Hooks eliminate duplication and make component intent clearer.
useInterval custom hook implementation
The useInterval hook accepts a callback function and a delay in milliseconds. It uses useEffect to set up an interval that calls the callback at the specified delay. The cleanup function calls clearInterval to prevent memory leaks. The dependency array includes both onTick and delay.
useInterval with useEffectEvent to prevent unnecessary re-runs
When using useInterval, if the callback function is included in the Effect dependency array, the interval will reset every time the component re-renders. To fix this, wrap the callback with useEffectEvent, which creates a stable reference that is not a dependency. This allows only the delay to be in the dependency array, preventing the interval from resetting unnecessarily.
useDelayedValue custom hook using setTimeout
The useDelayedValue hook creates a delayed version of a value. It stores the delayedValue as state and uses useEffect with a setTimeout to update it after the specified delay milliseconds have passed. The dependencies are [value, delay]. The Effect does not need cleanup because you want all scheduled timeouts to fire to keep movement continuous, not reset on re-renders.
use() hook for unwrapping promises in client components
The use() hook accepts a promise passed from a server component and unwraps it in a client component. If the promise is already resolved on the server before the component renders on the client, use() returns the value instantly without triggering a fallback. Example: 'use client'; import { use } from 'react'; export default function UserCard({ userPromise }) { const user = use(userPromise); return <div>{user.name}</div>; }
Custom hooks for context consumption
Export custom hooks like useTasks() and useTasksDispatch() from the context file. These hooks call useContext internally to access the contexts. This pattern simplifies consumption in child components and allows logic to be added to the hooks later. A function is a custom Hook if its name starts with 'use'.
useCallback with React.ChangeEventHandler
import { useState, useCallback } from 'react';
export default function Form() {
const [value, setValue] = useState("Change me");
const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
setValue(event.currentTarget.value);
}, [setValue])
return (
<>
<input value={value} onChange={handleChange} />
<p>Value: {value}</p>
</>
);
}
useCallback type inference in TypeScript
The function's type is inferred from the return value of the callback function. In TypeScript strict mode, useCallback requires adding types for the parameters in the callback.
Extract data fetching into custom Hook
Extract fetching logic into a custom Hook like `useData(url)` to make data fetching from Effects more ergonomic. This Hook should handle cleanup, error handling, and loading state. This makes it easier to move to a framework's built-in data fetching later.
Monitor and extract Effects into custom Hooks
When writing Effects, look for opportunities to extract a piece of functionality into a custom Hook with a more declarative and purpose-built API. Fewer raw useEffect calls in components make applications easier to maintain.