Advanced Hooks and Custom Hooks in React
1. useReducer Hook
The useReducer
hook is an alternative to useState
for managing more complex state logic. It is especially useful when the state has multiple sub-values or when next state depends on the previous state.
Syntax:
const [state, dispatch] = useReducer(reducer, initialState);
import React, { useReducer } from 'react';
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
Count: {state.count}
);
}
In this example, useReducer
manages the state of the counter. Instead of calling setState
, we dispatch actions (increment
or decrement
), and the reducer determines how the state should change.
2. useCallback Hook
useCallback
is used to memoize a function, ensuring it is not recreated on every render unless its dependencies change. This is useful when passing callbacks to child components, particularly to avoid unnecessary renders.
Syntax:
const memoizedCallback = useCallback(() => {
// Function logic
}, [dependencies]);
Example:
import React, { useState, useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return (
);
}
handleClick
function is memoized and won’t be recreated on every render unless its dependencies (in this case, none) change. This can improve performance in scenarios where functions are passed down to child components.
3. useMemo Hook
useMemo
is used to memoize a computed value, ensuring the value is recalculated only when its dependencies change. This is useful for performance optimization when expensive computations are involved.
Syntax:
const memoizedValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
Example:
import React, { useState, useMemo } from 'react';
function ExpensiveCalculationComponent({ number }) {
const computeFactorial = (n) => {
console.log('Computing factorial');
return n <= 0 ? 1 : n * computeFactorial(n - 1);
};
const factorial = useMemo(() => computeFactorial(number), [number]);
return Factorial of {number}: {factorial};
}
function App() {
const [number, setNumber] = useState(5);
return (
);
}
In this example, useMemo
ensures that the factorial is recalculated only when the number
changes, avoiding unnecessary recomputations.
4. useRef Hook
The useRef
hook is primarily used to persist values across renders without causing re-renders. It can also be used to directly access and manipulate DOM elements.
Syntax:
const refContainer = useRef(initialValue);
Example of Accessing DOM Elements:
import React, { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
return (
);
}
In this example, the useRef
hook stores a reference to the input element, and the handleFocus
function can programmatically focus the input when the button is clicked.
5. Creating Custom Hooks
Custom hooks allow you to extract and reuse logic across multiple components. This promotes cleaner code and reduces duplication. A custom hook is simply a function that uses one or more built-in hooks (like useState, useEffect, etc.) and returns values.
Example of a Custom Hook:
import { useState, useEffect } from 'react';
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}
function WindowWidthComponent() {
const width = useWindowWidth();
return Window width: {width}px;
}
Here’s how it works:
useWindowWidth
is a custom hook that tracks the width of the browser window.useState
is used to store the window width, and useEffect
is used to set up and clean up the event listener for the window resize event.
6. Using Custom Hooks for Data Fetching
A common use case for custom hooks is to manage data fetching logic, so it can be reused in multiple components.
Example:
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
function DataFetchingComponent() {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts');
if (loading) return Loading...
;
if (error) return Error: {error.message}
;
return (
{data.map(post => (
- {post.title}
))}
);
}
In this example:
useFetch
custom hook abstracts the data fetching logic, making it reusable for different URLs.
Conclusion
Advanced hooks like useReducer
, useCallback
, useMemo
, and useRef
offer more powerful state management, performance optimizations, and access to DOM elements in React components. Custom hooks allow for logic reusability, promoting cleaner, more maintainable code. By creating custom hooks, developers can extract logic such as data fetching, event handling, or any complex state management into reusable functions, improving code structure and maintainability.