Custom Hooks in React

What are Custom Hooks?

React custom hooks are reusable pieces of code that allow developers to extract stateful logic from components and make it more modular, composable, and easy to test. In essence, custom hooks are functions that use React hooks like useState, useEffect, useContext, and others, to abstract common behavior into a standalone module that can be easily imported and reused across different parts of an application. In this article, we will discuss some of the advantages of writing custom hooks in React, along with some examples that showcase their power and flexibility.

1. Simplify Component Logic

One of the main benefits of custom hooks is that they can help simplify complex component logic by encapsulating it into a separate module. For example, imagine you have a component that fetches data from an API and then displays it in a list. Instead of writing all the fetching and error handling logic inside the component, you can create a custom hook that handles this logic and returns the data and error state to the component. This makes the component cleaner, easier to read, and more focused on its UI responsibilities.

Here's an example of a custom hook that fetches data from an API:

import { useState, useEffect } from 'react';

export function useFetchData(url) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(url);
        const data = await response.json();
        setData(data);
      } catch (error) {
        setError(error);
      }
    }

    fetchData();
  }, [url]);

  return { data, error };
}

With this custom hook, you can fetch data from any API by simply passing the URL as a parameter, and then use the data and error state in your component as needed:

import { useFetchData } from './useFetchData';

function MyComponent() {
  const { data, error } = useFetchData('https://myapi.com/data');

  if (error) {
    return <div>{error.message}</div>;
  }

  if (!data) {
    return <div>Loading...</div>;
  }

  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

2. Reuse Code Across Components

Another advantage of custom hooks is that they enable code reuse across different components, without having to repeat the same logic in each component. For instance, if you have two components that share the same state and side effects, you can create a custom hook that encapsulates this logic and use it in both components. This not only reduces code duplication but also ensures consistency and maintainability.

Here's an example of a custom hook that handles a simple form state:

import { useState } from 'react';

export function useForm(initialState) {
  const [values, setValues] = useState(initialState);

  function handleChange(event) {
    setValues({ ...values, [event.target.name]: event.target.value });
  }

  function handleSubmit(callback) {
    return function(event) {
      event.preventDefault();
      callback(values);
    };
  }

  return { values, handleChange, handleSubmit };
}

With this custom hook, you can create a form component that handles its state and submit logic without repeating the same code:


import { useForm } from './useForm';

function MyForm() {
  const { values, handleChange, handleSubmit } = useForm({ name: '', email: '' });

  function handleFormSubmit(data) {
    // send data to API or do something else
  }

  return (
    <form onSubmit={handleSubmit(handleFormSubmit