React Design Patterns: Custom Hooks
Oct 20, 2026
Custom hooks allow us to combine existing React hooks such as useState and useEffect into reusable functions. Instead of repeating the same logic in several components, we can move that logic into one custom hook and call it wherever the same behavior is needed.
For example, a component can fetch its own user data, but another component may need the same request later. A custom hook lets us keep the data-loading logic in one place while the components focus on displaying the result.
In this article, we will create a hook that loads the current user. Then we will make it more reusable so it can load different users based on an ID.
What Is a Custom Hook?
A custom hook is a function that uses other React hooks. It can contain state, effects, and the logic connected to them. The custom hook returns the value that a component needs.
The basic idea looks like this:
const value = useCustomHook();The component calls the hook and stores its return value in a variable. The component does not need to repeat the internal state and effect logic because the custom hook takes care of it.
Custom hook names must begin with use. In this example, our hooks will be called useCurrentUser and useUser. A filename such as current-user.hook.js also makes it clear that the file contains a custom hook, especially when the project has several files with similar names.
Like container components and higher-order components, custom hooks allow us to share behavior between multiple components. The main difference in this case is that the reusable behavior is placed inside a hook function.
Creating a useCurrentUser Hook
The first custom hook will fetch the current user from the server. The server needs to be running while testing the example.
Create a file named current-user.hook.js:
import axios from "axios";
import { useEffect, useState } from "react";
export const useCurrentUser = () => {
const [user, setUser] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get("/current-user");
setUser(response.data);
})();
}, []);
return user;
};The user state begins as null because the data has not been loaded yet. The effect sends a request to the /current-user endpoint and saves the returned data in state.
The dependency array is empty, so the effect runs when the component first renders instead of running again after every render. Finally, the hook returns user. Any component that calls this hook receives the current value of that state.
Using the Hook in a Component
Before using the hook, a UserInfo component may receive the complete user through props. With useCurrentUser, the component no longer needs its parent to provide that user object.
import { useCurrentUser } from "./current-user.hook";
// Inside UserInfo:
const user = useCurrentUser();The value of user is the value returned from useCurrentUser. The hook handles the request and state, while UserInfo uses the result in its existing display code.
The component can then be rendered normally in App:
import { UserInfo } from "./UserInfo";
export default function App() {
return <UserInfo />;
}This works for the current user, but the endpoint is hardcoded. If we want to display several users, we need a more flexible hook.
Creating a Reusable useUser Hook
The improved hook receives a user ID as an argument. It uses that ID in the request URL so the same hook can fetch different users.
Create user.hook.js:
import axios from "axios";
import { useEffect, useState } from "react";
export const useUser = (userId) => {
const [user, setUser] = useState(null);
useEffect(() => {
(async () => {
const response = await axios.get(`/users/${userId}`);
setUser(response.data);
})();
}, [userId]);
return user;
};The main change is that useUser receives userId and includes it in the endpoint. The ID is also placed in the effect's dependency array. If the ID changes, the effect runs again and loads the new user.
The hook still returns the user state in the same way. The component only needs to provide an ID when calling it.
Passing the User ID Through Props
To reuse UserInfo for different users, the component can receive userId as a prop and pass it to the custom hook. The relevant part inside the component becomes:
import { useUser } from "./user.hook";
// Inside UserInfo, after receiving userId through props:
const user = useUser(userId);The parent does not need to fetch the complete user. It provides only the ID, and useUser takes care of loading the matching data.
We can now render the same component several times with different IDs:
export default function App() {
return (
<>
<UserInfo userId={1} />
<UserInfo userId={2} />
<UserInfo userId={3} />
</>
);
}Each UserInfo calls the same custom hook with a different value. The first instance loads user one, the second loads user two, and the third loads user three. The component and the data-loading logic are both reused.
Best Practices Shown by This Pattern
The examples demonstrate a few useful ideas:
- Start every custom hook name with
use. - Move repeated state and effect logic into a custom hook.
- Return the value that the component needs from the hook.
- Pass changing information, such as
userId, into the hook as an argument. - Include
userIdin the dependency array so a new user is loaded when the ID changes. - Use a clear filename such as
user.hook.jswhen it helps identify hook files in the project.
The purpose is to keep the loading behavior reusable. A component can call the hook without repeating the request, state, and effect every time.
Common Problems to Avoid
One problem is creating a custom hook without starting its name with use. React requires this naming convention for custom hooks, so a name such as currentUser should be changed to useCurrentUser.
Another problem is keeping the endpoint fixed when the hook needs to load different users. useCurrentUser is suitable for one particular endpoint, while useUser(userId) is more flexible because the ID is provided by the caller.
The dependency array also matters. When userId is used in the request, it needs to be included in that array. Otherwise, changing the ID will not cause the effect to load the new user. The server must also be running so the hook can receive data from the expected endpoints.
Summary
Custom hooks combine React hooks into reusable functions. They help us move shared behavior out of components and avoid repeating the same state and effect logic.
useCurrentUser loads one specific user from the /current-user endpoint. useUser improves that idea by receiving a user ID, requesting /users/${userId}, and running the effect again whenever that ID changes. Components can then reuse the hook and focus on displaying the returned data.