Custom hooks let you extract and reuse stateful logic in React functional components. This guide covers what they are, why to use them, and how to create one.
A custom hook is a JavaScript function that:
use (e.g., useCustomHook).useState, useEffect) to manage state or side effects.It’s a way to share logic, not UI, across components.

useState is a hook that helps with stateful logic by providing a way to store and update values within a component.useEffect to run actions after rendering.useEffect, the dependency array controls when the effect should run. If any value inside the array changes, the effect runs again. If the array is empty ([]), the effect runs only once when the component mounts.Identify repetitive logic (e.g., fetching data) and extract it into its own function.
use
Prefix the function with use to follow React’s hook convention. This is important because React hooks must start with use to differentiate them from regular functions.
Leverage useState, useEffect, and other React hooks within the custom hook to manage state or side effects.
Return the data or functions that components need to access, such as a value, setter, or function.
useFetch - Fetching DataHere’s a custom hook to fetch data:
// useFetch.js
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]); // Re-fetch if url changes
return { data, loading };
}
// Usage in a component
function App() {
const { data, loading } = useFetch('https://api.example.com/data');
return loading ? <p>Loading...</p> : <p>{data.message}</p>;
}
useState hook to track data and loading state. The useEffect hook performs a side effect—fetching data from the provided URL when the component mounts or when the url changes.useEffect hook.[url] tells React to re-run the fetch operation every time the url changes.useLocalStorage - Syncing State with Local StorageWhen to Use: When you want to persist simple state (e.g., a user preference) in local storage.
// useLocalStorage.js
import { useState } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
return localStorage.getItem(key) || initialValue;
});
const setStoredValue = (newValue) => {
setValue(newValue);
localStorage.setItem(key, newValue);
};
return [value, setStoredValue];
}
// Usage
function ThemeSwitcher() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Switch to {theme === 'light' ? 'Dark' : 'Light'}
</button>
);
}
useState hook with a function that initializes the state from localStorage or uses the provided initialValue if no value exists in storage.useState hook manages the state of the theme (either "light" or "dark").localStorage is a side effect that happens when the state changes.localStorage.setItem) occurs whenever the state changes.useCounter - Managing a CounterWhen to Use: When you need a reusable counter (e.g., for likes, scores) in multiple places.
// useCounter.js
import { useState } from 'react';
function useCounter(initialCount = 0) {
const [count, setCount] = useState(initialCount);
const increment = () => setCount(prev => prev + 1);
const decrement = () => setCount(prev => prev - 1);
const reset = () => setCount(initialCount);
return { count, increment, decrement, reset };
}
// Usage
function Counter() {
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={reset}>Reset</button>
</div>
);
}
useState to store and update the count value.useEffect or dependencies are involved here, but the hook relies solely on state changes to trigger re-renders.