Creating Custom Hooks for Reusable Logic

Loading

In React, hooks are a powerful feature that allows you to add state and side-effect logic to functional components. While React provides several built-in hooks such as useState, useEffect, useContext, etc., you can also create your own custom hooks to encapsulate and reuse logic across different components.

Custom hooks are essentially JavaScript functions that start with the prefix use and can use any of the built-in React hooks inside them. This allows you to create reusable and composable logic that can be shared across your React components.

1. Why Create Custom Hooks?

  • Reusability: Custom hooks enable you to reuse logic across multiple components without duplicating code.
  • Separation of Concerns: Custom hooks allow you to separate logic from the component’s rendering logic, making the component code cleaner and easier to maintain.
  • Encapsulation: You can encapsulate complex logic in custom hooks, making it easier to manage, test, and debug.

2. Basic Structure of a Custom Hook

A custom hook is a JavaScript function that:

  • Starts with use (e.g., useFetch, useLocalStorage, useCounter, etc.).
  • Can use React’s built-in hooks (useState, useEffect, etc.).
  • Returns values or functions that can be used by components.

Syntax:

function useCustomHook() {
  // Hook logic (e.g., state, side effects)
  
  return [state, actions]; // Return state or functions
}

3. Example 1: Custom Hook for Fetching Data

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

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

Explanation:

  • useFetch is a custom hook that takes a url as a parameter.
  • The hook uses useState to store the fetched data, loading state, and any error that occurs during the fetch.
  • The useEffect hook is used to perform the fetch operation when the url changes.
  • The hook returns an object with data, loading, and error, which can be used by the component that calls this custom hook.

Usage:

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

const App = () => {
  const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/posts');

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>Posts</h1>
      <ul>
        {data.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default App;

Explanation:

  • The useFetch hook is called in the App component to fetch posts from the API.
  • The data, loading, and error states returned from the hook are used to render different parts of the UI.

4. Example 2: Custom Hook for Managing Local Storage

Here’s a custom hook that helps manage values in localStorage:

import { useState } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue];
}

export default useLocalStorage;

Explanation:

  • useLocalStorage is a custom hook that allows you to read from and write to localStorage.
  • It takes two arguments: key (the localStorage key) and initialValue (the initial value to be stored if the localStorage item doesn’t exist).
  • The hook uses useState with a lazy initializer to retrieve the value from localStorage on the initial render.
  • The setValue function updates the value both in the state and in localStorage.

Usage:

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

const App = () => {
  const [name, setName] = useLocalStorage('name', 'John Doe');

  const handleChange = (e) => {
    setName(e.target.value);
  };

  return (
    <div>
      <h1>Your Name</h1>
      <input type="text" value={name} onChange={handleChange} />
      <p>Stored name: {name}</p>
    </div>
  );
};

export default App;

Explanation:

  • The useLocalStorage hook is used to store and retrieve the name value from localStorage.
  • When the user types in the input field, the name is updated in both the component’s state and in localStorage.

5. Example 3: Custom Hook for Counter Logic

Here’s a simple counter logic using a custom hook:

import { useState } from 'react';

function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(count + 1);
  const decrement = () => setCount(count - 1);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
}

export default useCounter;

Explanation:

  • useCounter is a custom hook for managing a counter state.
  • It provides functions to increment, decrement, and reset the counter value.

Usage:

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

const CounterApp = () => {
  const { count, increment, decrement, reset } = useCounter();

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

export default CounterApp;

Explanation:

  • The useCounter hook is used to manage and control the counter state. It returns the count value and functions to update it.

6. Best Practices for Custom Hooks

  • Keep It Simple: Custom hooks should encapsulate a single piece of reusable logic. Try to avoid making them too complex or containing unrelated functionality.
  • Use Built-in Hooks: You can use any of React’s built-in hooks inside your custom hooks (useState, useEffect, useContext, etc.) to build more complex logic.
  • Return Values: Custom hooks typically return an array or an object. Return only what the consuming component needs.
  • Encapsulate Complex Logic: Custom hooks are perfect for hiding complexity (e.g., managing forms, fetching data, dealing with timers, etc.).

Leave a Reply

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