Mastering React Custom Hooks: Logic Reusability
One of the most powerful features introduced in React 16.8 was Hooks. While useState and useEffect are the bread and butter of modern React development, the real magic happens when you compose them into Custom Hooks.
If you find yourself writing the same useEffect code in multiple components—perhaps to fetch data or listen to window resizing events—it is time to refactor.
The Anatomy of a Custom Hook
A custom hook is simply a JavaScript function whose name starts with "use" and that may call other hooks. Let's look at a practical example: Data Fetching.
The Problem: Repetitive Fetch Logic
In a typical component, you might see this:
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []);
Writing this in every component is tedious and violates the DRY (Don't Repeat Yourself) principle.
The Solution: useFetch
Let's extract this into a hook file useFetch.js:
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch(url, { signal: abortCont.signal })
.then(res => {
if (!res.ok) {
throw Error('Could not fetch the data for that resource');
}
return res.json();
})
.then(data => {
setData(data);
setLoading(false);
setError(null);
})
.catch(err => {
if (err.name === 'AbortError') {
console.log('fetch aborted');
} else {
setLoading(false);
setError(err.message);
}
});
return () => abortCont.abort();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Implementing the Hook
Now, your component becomes significantly cleaner:
import useFetch from './useFetch';
const BlogList = () => {
const { data: blogs, loading, error } = useFetch('https://api.example.com/blogs');
return (
<div className="blog-list">
{ error && <div>{ error }</div> }
{ loading && <div>Loading...</div> }
{ blogs && <BlogList blogs={blogs} /> }
</div>
);
}
Advanced Use Cases
Custom hooks aren't just for data. You can use them for:
- Form Handling:
useFormto manage input state and validation. - Media Queries:
useMediaQueryto conditionally render layouts based on screen size. - Local Storage:
useLocalStorageto sync state automatically with the browser's storage.
Conclusion
Custom hooks allow you to separate logic from UI. By moving complex logic into hooks, your components become purely presentational, making them easier to read, test, and maintain.