How to Build a Custom Hook in React (Best Practices)

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?

Custom Hook is a JavaScript function whose name starts with use and can call other Hooks (like useStateuseEffect, 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 modularcleaner, 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:

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. 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 in localStorage.
    • The hook will return the current theme and a function to toggle between light and dark modes.
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 current theme (either 'light' or 'dark') and the toggleTheme 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 a useEffect 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!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *