React Custom Hooks
Overview
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.
What is a Custom Hook?
A custom hook is a JavaScript function that:
- Starts with
use(e.g.,useCustomHook). - Uses React’s built-in hooks (like
useState,useEffect) to manage state or side effects. - Returns values or functions for components to use.
It’s a way to share logic, not UI, across components.
Why Create a Custom Hook?
- Reuse Logic: Avoid duplicating code (e.g., fetching data, managing forms).
- Cleaner Components: Move complex logic out of components for better readability.
- Encapsulation: Keep related state and behavior together.

Key Vocabulary:
- Stateful Logic: Logic that involves managing and manipulating data that can change over time (e.g., form values, counters, etc.). For example,
useStateis a hook that helps with stateful logic by providing a way to store and update values within a component. - Side Effects: Operations that interact with the outside world, such as fetching data or modifying the DOM. In React, side effects are often handled with
useEffectto run actions after rendering. - Dependency Arrays: In
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.
How to Create a Custom Hook?
1. Move Logic into a Reusable Function
Identify repetitive logic (e.g., fetching data) and extract it into its own function.
2. Name it with 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.
3. Use Built-in Hooks Inside
Leverage useState, useEffect, and other React hooks within the custom hook to manage state or side effects.
4. Return State/Functions
Return the data or functions that components need to access, such as a value, setter, or function.
Examples
Example 1: useFetch - Fetching Data
Here’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>;
}
- Logic: This custom hook uses the
useStatehook to track data and loading state. TheuseEffecthook performs a side effect—fetching data from the provided URL when the component mounts or when theurlchanges. - Vocabulary:
- Side Effects: In this case, the side effect is fetching data from an external API, which occurs within the
useEffecthook. - Dependency Array: The dependency array
[url]tells React to re-run the fetch operation every time theurlchanges.
- Side Effects: In this case, the side effect is fetching data from an external API, which occurs within the
Example 2: useLocalStorage - Syncing State with Local Storage
When 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>
);
}
- Logic: This hook syncs a state value with local storage. It uses the
useStatehook with a function that initializes the state fromlocalStorageor uses the providedinitialValueif no value exists in storage. - Vocabulary:
- Stateful Logic: The
useStatehook manages the state of the theme (either “light” or “dark”). - Side Effects: Storing the state in
localStorageis a side effect that happens when the state changes. - Dependency Array: There’s no explicit dependency array here, but the side effect (
localStorage.setItem) occurs whenever the state changes.
- Stateful Logic: The
Example 3: useCounter - Managing a Counter
When 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>
);
}
- Logic: This custom hook manages a counter with functions to increment, decrement, and reset the count. It uses
useStateto store and update the count value. - Vocabulary:
- Stateful Logic: The counter is an example of stateful logic—tracking a value (count) that can change over time.
- Side Effects: This example doesn’t have side effects but can be extended to do so, such as saving the count in local storage or a backend.
- Dependency Arrays: No
useEffector dependencies are involved here, but the hook relies solely on state changes to trigger re-renders.