In React, hooks are a powerful feature that allows you to add state and side-effect logic to functional components. While React provides several built-in hooks such as useState
, useEffect
, useContext
, etc., you can also create your own custom hooks to encapsulate and reuse logic across different components.
Custom hooks are essentially JavaScript functions that start with the prefix use
and can use any of the built-in React hooks inside them. This allows you to create reusable and composable logic that can be shared across your React components.
1. Why Create Custom Hooks?
- Reusability: Custom hooks enable you to reuse logic across multiple components without duplicating code.
- Separation of Concerns: Custom hooks allow you to separate logic from the component’s rendering logic, making the component code cleaner and easier to maintain.
- Encapsulation: You can encapsulate complex logic in custom hooks, making it easier to manage, test, and debug.
2. Basic Structure of a Custom Hook
A custom hook is a JavaScript function that:
- Starts with
use
(e.g.,useFetch
,useLocalStorage
,useCounter
, etc.). - Can use React’s built-in hooks (
useState
,useEffect
, etc.). - Returns values or functions that can be used by components.
Syntax:
function useCustomHook() {
// Hook logic (e.g., state, side effects)
return [state, actions]; // Return state or functions
}
3. Example 1: Custom Hook for Fetching Data
Here’s an example of a custom hook that fetches data from an API:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Explanation:
useFetch
is a custom hook that takes aurl
as a parameter.- The hook uses
useState
to store the fetcheddata
,loading
state, and anyerror
that occurs during the fetch. - The
useEffect
hook is used to perform the fetch operation when theurl
changes. - The hook returns an object with
data
,loading
, anderror
, which can be used by the component that calls this custom hook.
Usage:
import React from 'react';
import useFetch from './useFetch';
const App = () => {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts');
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Posts</h1>
<ul>
{data.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
};
export default App;
Explanation:
- The
useFetch
hook is called in theApp
component to fetch posts from the API. - The
data
,loading
, anderror
states returned from the hook are used to render different parts of the UI.
4. Example 2: Custom Hook for Managing Local Storage
Here’s a custom hook that helps manage values in localStorage
:
import { useState } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
export default useLocalStorage;
Explanation:
useLocalStorage
is a custom hook that allows you to read from and write tolocalStorage
.- It takes two arguments:
key
(thelocalStorage
key) andinitialValue
(the initial value to be stored if thelocalStorage
item doesn’t exist). - The hook uses
useState
with a lazy initializer to retrieve the value fromlocalStorage
on the initial render. - The
setValue
function updates the value both in the state and inlocalStorage
.
Usage:
import React from 'react';
import useLocalStorage from './useLocalStorage';
const App = () => {
const [name, setName] = useLocalStorage('name', 'John Doe');
const handleChange = (e) => {
setName(e.target.value);
};
return (
<div>
<h1>Your Name</h1>
<input type="text" value={name} onChange={handleChange} />
<p>Stored name: {name}</p>
</div>
);
};
export default App;
Explanation:
- The
useLocalStorage
hook is used to store and retrieve thename
value fromlocalStorage
. - When the user types in the input field, the
name
is updated in both the component’s state and inlocalStorage
.
5. Example 3: Custom Hook for Counter Logic
Here’s a simple counter logic using a custom hook:
import { useState } from 'react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
export default useCounter;
Explanation:
useCounter
is a custom hook for managing a counter state.- It provides functions to
increment
,decrement
, andreset
the counter value.
Usage:
import React from 'react';
import useCounter from './useCounter';
const CounterApp = () => {
const { count, increment, decrement, reset } = useCounter();
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
<button onClick={reset}>Reset</button>
</div>
);
};
export default CounterApp;
Explanation:
- The
useCounter
hook is used to manage and control the counter state. It returns thecount
value and functions to update it.
6. Best Practices for Custom Hooks
- Keep It Simple: Custom hooks should encapsulate a single piece of reusable logic. Try to avoid making them too complex or containing unrelated functionality.
- Use Built-in Hooks: You can use any of React’s built-in hooks inside your custom hooks (
useState
,useEffect
,useContext
, etc.) to build more complex logic. - Return Values: Custom hooks typically return an array or an object. Return only what the consuming component needs.
- Encapsulate Complex Logic: Custom hooks are perfect for hiding complexity (e.g., managing forms, fetching data, dealing with timers, etc.).