techStackGuru

React Custom Hooks


With custom hooks in React, you can reuse stateful logic across multiple components. As a result, components can easily share common logic without having to deal with inheritance or complex patterns like higher-order components (HOCs) or render props.

It is important to follow a naming convention when naming custom hooks, where the method name begins with "use." This convention allows React to recognise the custom hook as a hook.

Here is an example of a custom hook called useFetch.

import { useState, useEffect } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
        setLoading(false);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading };
} 

Using the above example, the useFetch hook sets two state variables: data and loading. Once the component mounts or when the URL changes, it uses the useEffect hook to fetch data from the URL. A data state variable is used for storing fetched data, while a loading state variable is set to true as the data is fetched. Additionally, the custom hook returns an object containing the data and loading values, so the consuming component can make use of them.

Custom hooks can be imported and invoked just like any other hooks within a component:

import React from 'react';
import useFetch from './useFetch';

function MyComponent() {
  const { data, loading } = useFetch('https://api.example.com/data');

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

  return (
    <div>
      <h1>Data:</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
} 

Using the useFetch hook, data is fetched from a specific URL inside the MyComponent component. A loading message can be rendered conditionally while data is being fetched by using the loading flag. Using the pre tag, the data is displayed once it is available.

Example - useLocalStorage

import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const storedValue = localStorage.getItem(key);
    return storedValue ? JSON.parse(storedValue) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
} 

Usage:

import React from 'react';
import useLocalStorage from './useLocalStorage';

function MyComponent() {
  const [name, setName] = useLocalStorage('name', '');

  return (
    <div>
      <input
        type="text"
        value={name}
        onChange={event => setName(event.target.value)}
      />
      <p>Hello, {name}!</p>
    </div>
  );
} 

By using this hook, you can synchronize a value with the browser's local storage. As a result, data is persistent across page reloads and even different browser sessions when it is stored in local storage.