Skip to main content

What are Custom Hooks?

Custom hooks allow you to extract component logic into reusable functions. They let you share stateful logic between components without changing your component hierarchy.
Custom hooks must start with “use” and can call other hooks inside them.

Creating a Custom Hook

Here’s a real example from the course that creates a reusable useFetch hook for handling HTTP requests:
import { useEffect, useState } from 'react';

export function useFetch(fetchFn, initialValue) {
  const [isFetching, setIsFetching] = useState();
  const [error, setError] = useState();
  const [fetchedData, setFetchedData] = useState(initialValue);

  useEffect(() => {
    async function fetchData() {
      setIsFetching(true);
      try {
        const data = await fetchFn();
        setFetchedData(data);
      } catch (error) {
        setError({ message: error.message || 'Failed to fetch data.' });
      }

      setIsFetching(false);
    }

    fetchData();
  }, [fetchFn]);

  return {
    isFetching,
    fetchedData,
    error
  }
}

Using Custom Hooks

Once created, you can use your custom hook in any component:
function AvailablePlaces({ onSelectPlace }) {
  const {
    isFetching,
    fetchedData: availablePlaces,
    error
  } = useFetch(fetchPlaces, []);

  if (error) {
    return <Error title="An error occurred!" message={error.message} />;
  }

  return (
    <Places
      title="Available Places"
      places={availablePlaces}
      isLoading={isFetching}
      loadingText="Fetching place data..."
      fallbackText="No places available."
      onSelectPlace={onSelectPlace}
    />
  );
}
The useFetch hook encapsulates all the loading, error, and data state management, making your components cleaner.

Custom Input Hook Example

Here’s another practical example - a useInput hook for managing form inputs with validation:
import { useState } from 'react';

export function useInput(defaultValue, validationFn) {
  const [enteredValue, setEnteredValue] = useState(defaultValue);
  const [didEdit, setDidEdit] = useState(false);

  const valueIsValid = validationFn(enteredValue);

  function handleInputChange(event) {
    setEnteredValue(event.target.value);
    setDidEdit(false);
  }

  function handleInputBlur() {
    setDidEdit(true);
  }

  return {
    value: enteredValue,
    handleInputChange,
    handleInputBlur,
    hasError: didEdit && !valueIsValid
  };
}

Best Practices

Custom hook names must start with “use” so React can automatically check for violations of Hook rules.
Each call to a custom hook gets its own isolated state. The hook shares stateful logic, not the state itself.
Design your hooks to return the values and functions that components actually need to work with.
Create hooks with a single, clear purpose. Complex logic can be split into multiple hooks.

Key Takeaways

Reusability

Extract common logic into custom hooks to avoid code duplication across components

Composition

Custom hooks can use other hooks, including other custom hooks, for powerful compositions

Encapsulation

Hide complex logic behind a simple interface that components can easily consume

Testing

Custom hooks can be tested independently, making your application more maintainable
Source Code: Section 16 - Custom Hooks

Build docs developers (and LLMs) love