React Custom Hooks
# React Custom Hooks
1. Introduction
As your applications grow, you will find yourself writing the exact sameuseState and useEffect logic in multiple components. For example, fetching data from an API usually requires loading, error, and data states. Repeating this in 10 different files is bad practice. To solve this, React allows you to create Custom Hooks: reusable JavaScript functions that encapsulate stateful logic.
2. Learning Objectives
By the end of this chapter, you will be able to:- Identify repeated logic that can be extracted into a Custom Hook.
- Follow the naming conventions for Custom Hooks.
- Create a reusable data-fetching hook.
- Understand how custom hooks share logic, not state.
3. Beginner-Friendly Explanations
What is a Custom Hook?
A custom hook is simply a regular JavaScript function. The only difference is:-
1.
Its name must start with
use(e.g.,useFetch,useWindowSize,useAuth).
-
2.
It can call other React Hooks (like
useStateanduseEffect) inside of it.
If you extract UI into a reusable Component, you extract Logic into a reusable Custom Hook.
Sharing Logic, NOT State
If Component A and Component B both use a customuseCounter hook, they do not share the same count. They each get a completely independent copy of the state. Custom hooks reuse the *behavior*, not the *data*.
4. Syntax Explanation
Step 1: Identifying Repeated Logic
Imagine we need to track the window width in multiple components.Without Custom Hook (Messy): ```jsx id="ch22_ex1" function ComponentA() { const [width, setWidth] = useState(window.innerWidth); useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return <div>Width is {width}</div>; }
jsx id="ch22_ex2" // useWindowWidth.js import { useState, useEffect } from 'react';
// 1. Name starts with 'use' export function useWindowWidth() { const [width, setWidth] = useState(window.innerWidth);
useEffect(() => { const handleResize = () => setWidth(window.innerWidth); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []);
// 2. Return the data you want the components to access return width; }
jsx id="ch22_ex3" // ComponentA.jsx import { useWindowWidth } from './useWindowWidth';
function ComponentA() { const width = useWindowWidth(); // 1 line of code! return <div>Width is {width}</div>; }
jsx id="ch22proj1a" // Example React Custom Hook import { useState, useEffect } from 'react';
export function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const response = await fetch(url);
if (!response.ok) throw new Error(HTTP error! status: ${response.status});
const json = await response.json();
setData(json);
setError(null);
} catch (e) {
setError(e.message);
} finally {
setLoading(false);
}
};
fetchData(); }, [url]); // Re-run if the URL changes
// Return as an object for easy destructuring return { data, loading, error }; }
jsx id="ch22proj1b" // Example React component import React from 'react'; import { useFetch } from './useFetch';
export default function UserDashboard() { // Look how clean this is! const { data: users, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');
return ( <div className="min-h-screen bg-slate-50 p-8 font-sans"> <div className="max-w-2xl mx-auto bg-white p-8 rounded-2xl shadow border border-slate-100"> <h1 className="text-3xl font-black text-slate-800 mb-6">User Directory</h1>
{loading && ( <div className="flex justify-center my-10"> <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div> </div> )}
{error && ( <div className="bg-red-50 text-red-600 p-4 rounded-lg font-medium border border-red-200"> Error loading users: {error} </div> )}
{!loading && !error && users && (
<ul className="space-y-3">
{users.map(user => (
<li key={user.id} className="p-4 bg-slate-50 rounded-lg flex items-center justify-between border border-slate-200 hover:border-blue-300 transition-colors">
<span className="font-bold text-slate-700">{user.name}</span>
<span className="text-sm text-slate-500 font-medium">{user.email}</span>
</li>
))}
</ul>
)}
</div>
</div>
);
}
``
11. Coding Challenges
Challenge 1: Create a useLocalStorage(key, initialValue) hook. It should initialize state by reading from window.localStorage, and every time the state is updated via the setter, it should also save the new value back to localStorage.
12. MCQs with Answers
Q1: What is the strict naming requirement for Custom Hooks?
A) They must end with "Hook".
B) They must start with "use" (lowercase).
C) They must be named in all uppercase.
*Answer: B*
Q2: If two components use the same
useCounter custom hook, do they share the same count state?
A) Yes, custom hooks act as global state.
B) No, each component gets a completely independent instance of the state.
*Answer: B*
13. Interview Questions
-
Q: What is the primary purpose of writing a Custom Hook?
-
Q: Can a Custom Hook return JSX? *(Answer: No, if it returns JSX, it is a Component. Hooks return data or functions).*
14. FAQs
Are there libraries for Custom Hooks?
Yes! Libraries like react-use or usehooks-ts provide dozens of pre-written custom hooks (like useHover, useDebounce, useGeolocation) that you can install via npm.
15. Summary
Custom Hooks are the ultimate tool for logic reusability. By wrapping complex useState and useEffect logic inside a function starting with use`, you can clean up your components, drastically reduce code duplication, and build a library of highly useful tools.