Published on 20.12.2020
React is all about components and state management, but sometimes the logic for managing state can get repetitive. That’s where Custom Hooks come in. Custom hooks allow you to extract and reuse stateful logic across multiple components, making your code cleaner, more readable, and maintainable.
In this post, we will explore:
- Why custom Hooks improve code reusability in React.
- How to create a
useDarkMode
hook as an example. - Best practices to follow when writing custom hooks to avoid common pitfalls.
Let’s dive in!
What is a Custom Hook?
A Custom Hook is a JavaScript function whose name starts with use
and can call other Hooks (like useState
, useEffect
, etc.). Custom hooks allow you to extract stateful logic from a component so that it can be reused across different components. They don’t return JSX; instead, they return values or functions that can be used in your components.
The benefit of custom hooks is that they make your code more modular, cleaner, and easier to manage by allowing the reuse of logic. For example, rather than copying the same logic into several components, you can create a custom hook and call it wherever needed.
Why Custom Hooks Improve Code Reusability
Custom hooks provide a simple yet powerful abstraction for managing state, side effects, and other logic in React. Here’s why they improve code reusability:
- Encapsulation of Logic: By using custom hooks, you encapsulate the logic of managing states, handling side effects, or performing any other operations. This means your components can remain simpler and cleaner.
- Separation of Concerns: Custom hooks allow you to separate state logic and UI components. Your components should focus on rendering UI, while custom hooks take care of the logic. This leads to better-organized code.
- Code Reusability: Once you write a custom hook, you can reuse it across multiple components. This reduces redundancy and makes it easy to maintain the application.
- Maintainability: Managing and modifying shared logic is easier with custom hooks. If something needs to be changed in the logic, you only need to update it in one place.
Creating a useDarkMode
Custom Hook Example
Let’s create a custom hook called useDarkMode
to manage dark mode and light mode preferences for our application. This hook will allow the app to switch between light and dark themes and persist the user’s theme choice in localStorage
.
Step-by-Step Implementation
- Set up the
useDarkMode
hook:- We’ll use
useState
to store the current theme. - We’ll also use
useEffect
to apply the theme on initial load and save the selected theme inlocalStorage
. - The hook will return the current theme and a function to toggle between light and dark modes.
- We’ll use
import { useState, useEffect } from 'react';
function useDarkMode() {
// Get the initial theme from localStorage or default to light
const [theme, setTheme] = useState(() => {
const savedTheme = localStorage.getItem('theme');
return savedTheme ? savedTheme : 'light';
});
// Update localStorage whenever the theme changes
useEffect(() => {
localStorage.setItem('theme', theme);
// Apply the theme to the body element
document.body.className = theme;
}, [theme]);
// Function to toggle between light and dark mode
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return [theme, toggleTheme];
}
export default useDarkMode;
Using the useDarkMode
Hook in a Component
Now, we can use this custom hook in a component:
import React from 'react';
import useDarkMode from './useDarkMode';
function App() {
const [theme, toggleTheme] = useDarkMode();
return (
<div>
<h1>Current Theme: {theme}</h1>
<button onClick={toggleTheme}>Toggle Dark Mode</button>
</div>
);
}
export default App;
How It Works:
useDarkMode
returns the currenttheme
(either'light'
or'dark'
) and thetoggleTheme
function that switches between the two.- When the theme changes,
localStorage
is updated with the new theme and the body element is updated with the corresponding class. - The custom hook can now be reused across any component where theme toggling is needed.
Best Practices for Writing Custom Hooks
While custom hooks are an excellent tool for code reuse, it’s important to follow certain best practices to avoid common pitfalls.
1. Keep Custom Hooks Focused
Each custom hook should have a clear, single responsibility. Don’t try to combine unrelated logic into a single hook. This makes your hooks easier to test and reuse.
Bad Example:
// This hook does too many things: manages theme, window size, and localStorage.
function useAppSettings() {
const [theme, setTheme] = useState('light');
const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight });
useEffect(() => {
localStorage.setItem('theme', theme);
window.addEventListener('resize', () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
});
}, [theme, windowSize]);
return { theme, windowSize };
}
Better Example:
Separate the concerns into different hooks:
function useTheme() {
const [theme, setTheme] = useState('light');
useEffect(() => {
localStorage.setItem('theme', theme);
}, [theme]);
return [theme, setTheme];
}
function useWindowSize() {
const [windowSize, setWindowSize] = useState({ width: window.innerWidth, height: window.innerHeight });
useEffect(() => {
const handleResize = () => {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
2. Don’t Call Hooks Conditionally
Hooks should be called at the top level of the component or custom hook. This ensures they always run in the same order on every render.
Bad Example:
function useConditionalEffect(shouldRun) {
if (shouldRun) {
useEffect(() => {
console.log('Effect runs');
}, []);
}
}
Better Example:
function useConditionalEffect(shouldRun) {
useEffect(() => {
if (shouldRun) {
console.log('Effect runs');
}
}, [shouldRun]);
}
3. Use Descriptive Names
Your custom hook name should describe what it does clearly. Stick to the use<Feature>
convention to maintain consistency.
For example:
useDarkMode()
useLocalStorage()
useWindowSize()
4. Return Only What’s Necessary
Custom hooks should only return the data and functions that are necessary for the consuming component. This reduces complexity and keeps your components focused on rendering.
Avoiding Common Pitfalls When Writing Custom Hooks
- Don’t rely too much on
useEffect
for side-effects. If your logic involves complex side effects, think carefully about separating concerns and whether using auseEffect
is the best choice. - Avoid modifying state directly. Always use React’s state setters (e.g.,
setState
) instead of directly modifying the state, as this can lead to unexpected behavior. - Ensure that custom hooks are idempotent. They should always return the same output given the same input, and not cause side effects unless explicitly desired.
Conclusion
Custom Hooks are a powerful feature in React that can significantly improve code reusability, readability, and maintainability. By extracting logic into custom hooks, you can keep your components clean and focused on rendering UI.
Remember to keep your hooks focused, avoid calling them conditionally, and use descriptive names. With these best practices, you’ll be able to create reusable and robust hooks that will improve your React development experience.
Start creating your own custom hooks today, and watch your code become more modular and easier to maintain!