A Custom Hook is a reusable JavaScript function that uses one or more React Hooks. Custom Hooks allow developers to share stateful logic between multiple components without duplicating the same code.
use, such as useCounter,
useFetch, or useForm.
A Custom Hook is a JavaScript function created by the developer to reuse React Hook logic across components.
function useCounter() {
// Hook logic
}
Custom Hooks help keep components smaller and make reusable logic easier to maintain.
Suppose several components need the same state and event logic. Instead of copying the same code into every component, the logic can be placed inside a Custom Hook.
Custom Hook names should start with use.
useCounter()
useForm()
useFetch()
useWindowSize()
useLocalStorage()
Starting the name with use follows React's Hook naming
convention.
function useSomething() {
// React Hook logic
return value;
}
A Custom Hook can use React Hooks and return values that a component needs.
A Custom Hook can use useState.
import { useState } from "react";
function useCounter() {
const [count, setCount] =
useState(0);
return {
count,
setCount
};
}
The component can use the returned values.
function Counter() {
const {
count,
setCount
} = useCounter();
return (
<button
onClick={() =>
setCount(count + 1)
}
>
{count}
</button>
);
}
import { useState } from "react";
function useCounter(initialValue = 0) {
const [count, setCount] =
useState(initialValue);
function increment() {
setCount(
previous =>
previous + 1
);
}
function decrement() {
setCount(
previous =>
previous - 1
);
}
function reset() {
setCount(initialValue);
}
return {
count,
increment,
decrement,
reset
};
}
export default useCounter;
import useCounter
from "./useCounter";
function App() {
const {
count,
increment,
decrement,
reset
} = useCounter(10);
return (
<div>
<h2>
Count: {count}
</h2>
<button onClick={increment}>
+
</button>
<button onClick={decrement}>
-
</button>
<button onClick={reset}>
Reset
</button>
</div>
);
}
A Custom Hook can use React Hooks such as:
useStateuseEffectuseRefuseContextuseMemouseCallbackThis makes Custom Hooks useful for packaging reusable stateful logic.
A Custom Hook can combine useState and
useEffect.
import {
useEffect,
useState
} from "react";
function useDocumentTitle(title) {
useEffect(() => {
document.title = title;
}, [title]);
}
export default useDocumentTitle;
function Profile() {
useDocumentTitle(
"Student Profile"
);
return (
<h1>
Student Profile
</h1>
);
}
The Custom Hook hides the document-title logic from the component.
function useOnlineStatus() {
const [online, setOnline] =
useState(
navigator.onLine
);
useEffect(() => {
function handleOnline() {
setOnline(true);
}
function handleOffline() {
setOnline(false);
}
window.addEventListener(
"online",
handleOnline
);
window.addEventListener(
"offline",
handleOffline
);
return () => {
window.removeEventListener(
"online",
handleOnline
);
window.removeEventListener(
"offline",
handleOffline
);
};
}, []);
return online;
}
function Status() {
const online =
useOnlineStatus();
return (
<h2>
{online
? "Online"
: "Offline"}
</h2>
);
}
The component does not need to know how the online status is detected.
Form logic is a common use case for Custom Hooks.
function useInput(initialValue = "") {
const [value, setValue] =
useState(initialValue);
function handleChange(event) {
setValue(event.target.value);
}
return {
value,
onChange: handleChange
};
}
function Login() {
const username =
useInput("");
const password =
useInput("");
return (
<form>
<input
type="text"
placeholder="Username"
{...username}
/>
<input
type="password"
placeholder="Password"
{...password}
/>
</form>
);
}
The spread operator passes the returned value and
onChange properties to the inputs.
A Custom Hook can package common API-loading logic.
function useFetch(url) {
const [data, setData] =
useState(null);
const [loading, setLoading] =
useState(true);
const [error, setError] =
useState(null);
// Fetch logic here
return {
data,
loading,
error
};
}
function useFetch(url) {
const [data, setData] =
useState(null);
const [loading, setLoading] =
useState(true);
const [error, setError] =
useState(null);
useEffect(() => {
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(
"Request failed"
);
}
return response.json();
})
.then(result => {
setData(result);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
}, [url]);
return {
data,
loading,
error
};
}
function Students() {
const {
data,
loading,
error
} = useFetch(
"/api/students"
);
if (loading) {
return <p>
Loading...
</p>;
}
if (error) {
return <p>
Error loading data
</p>;
}
return (
<div>
{data.map(student => (
<p key={student.id}>
{student.name}
</p>
))}
</div>
);
}
Custom Hooks can accept parameters just like normal JavaScript functions.
function useCounter(initialValue) {
const [count, setCount] =
useState(initialValue);
return {
count,
setCount
};
}
A component can provide a different initial value.
const counter =
useCounter(100);
A Custom Hook can return an object containing multiple values and functions.
return {
value,
loading,
error,
reset,
submit
};
The component can destructure only the values it needs.
A Custom Hook can also return an array.
return [
value,
setValue
];
The component can use array destructuring.
const [
value,
setValue
] = useCustomHook();
Custom Hooks follow the same Rules of Hooks as built-in Hooks.
use.If two components call the same Custom Hook, they normally receive their own independent state.
function CounterOne() {
const counter =
useCounter(0);
return (
<h2>
{counter.count}
</h2>
);
}
function CounterTwo() {
const counter =
useCounter(0);
return (
<h2>
{counter.count}
</h2>
);
}
The state belongs to each component instance.
| Normal Function | Custom Hook |
|---|---|
| General JavaScript logic. | Reusable React stateful logic. |
| Does not normally use React Hooks. | Can use React Hooks. |
| Can have any valid function name. | Usually starts with use. |
| Used for general calculations or utilities. | Used for reusable component logic. |
A common project structure is to keep Custom Hooks in a dedicated
hooks folder.
src/
│
├── components/
│
├── pages/
│
├── hooks/
│ ├── useCounter.js
│ ├── useFetch.js
│ └── useForm.js
│
├── App.jsx
└── main.jsx
This makes reusable Hook logic easy to find.
A Custom Hook can encapsulate local storage logic.
function useLocalStorage(
key,
initialValue
) {
const [value, setValue] =
useState(() => {
const saved =
localStorage.getItem(key);
return saved
? JSON.parse(saved)
: initialValue;
});
useEffect(() => {
localStorage.setItem(
key,
JSON.stringify(value)
);
}, [key, value]);
return [
value,
setValue
];
}
function App() {
const [
name,
setName
] = useLocalStorage(
"name",
""
);
return (
<div>
<input
value={name}
onChange={e =>
setName(
e.target.value
)
}
/>
<p>
Name: {name}
</p>
</div>
);
}
The component can use local storage without directly handling the storage implementation.
use.useEffect.The general workflow for creating a Custom Hook is:
use.Repeated Logic
↓
Create Custom Hook
↓
useState / useEffect / Other Hooks
↓
Return Values
↓
Use in Components
use.hooks folder when appropriate.use.useState and useEffect.useCounter, useFetch, and useForm.Question: What is the main purpose of a Custom Hook in React?