Instagram
youtube
Facebook
Twitter

Custom Hooks

Introduction

In React, custom hooks allow you to extract and reuse component logic in a modular way. They are essentially JavaScript functions that use built-in React hooks (useState, useEffect, etc.) to encapsulate behavior, making it reusable across different components. Custom hooks improve code readability, reduce duplication, and provide an efficient way to manage complex logic in functional components.

In this lesson, you will learn:

  • What custom hooks are and why they are useful
  • How to create custom hooks with examples
  • Common use cases for custom hooks in React applications

What Are Custom Hooks?

A custom hook is a function that starts with the word “use” and allows you to reuse stateful logic across different components. Custom hooks can handle various behaviors, such as data fetching, form handling, or complex calculations, and encapsulate these behaviors into a single reusable function.

Benefits of Using Custom Hooks

  1. Code Reusability: Custom hooks let you avoid duplicating code by extracting and reusing logic across multiple components.
  2. Separation of Concerns: Custom hooks help keep your component logic organized, separating data management and side effects from the main component structure.
  3. Readability and Maintainability: By encapsulating complex behaviors, custom hooks make components simpler and easier to maintain.

Creating a Custom Hook

To create a custom hook, define a function that starts with "use" and call any necessary hooks (like useState or useEffect) inside this function. Custom hooks can accept parameters and return values, which makes them flexible and easy to adapt.

Basic Structure of a Custom Hook:

function useCustomHook(parameter) {
  // Use built-in hooks or custom logic
  const [state, setState] = useState(initialValue);

  // Logic or effect
  useEffect(() => {
    // Side effect logic here
  }, [parameter]);

  // Return values that the component can use
  return [state, setState];
}

Example 1: Custom Hook for Data Fetching

Fetching data is a common task in React applications. With a custom hook, you can encapsulate this logic and reuse it wherever you need to fetch data.

Creating the useFetch Hook

This custom hook fetches data from a given API endpoint and returns the data, loading status, and any errors.

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);
        if (!response.ok) throw new Error('Failed to fetch data');
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

In this example:

  • useFetch accepts a url parameter.
  • It initializes data, loading, and error states.
  • The fetchData function handles the API call, error handling, and loading state.

Using useFetch in a Component

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

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

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error}</p>;
  return <div>{JSON.stringify(data)}</div>;
}

This component uses useFetch to retrieve data and conditionally renders the loading or error message based on the hook’s state.


Example 2: Custom Hook for Form Handling

Managing form inputs and validation can become repetitive. A custom hook can simplify form state management and handle input changes.

Creating the useForm Hook

This custom hook manages form state and handles input changes.

import { useState } from 'react';

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

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

  return { values, handleChange };
}

export default useForm;

In this example:

  • useForm accepts initialValues, an object with default values for form inputs.
  • values holds the current form state, while handleChange updates the form state based on user input.

Using useForm in a Component

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

function FormComponent() {
  const { values, handleChange } = useForm({ username: '', email: '' });

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted:', values);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Username:
        <input type="text" name="username" value={values.username} onChange={handleChange} />
      </label>
      <label>
        Email:
        <input type="email" name="email" value={values.email} onChange={handleChange} />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}

This form component uses useForm to manage the state of its username and email inputs, making the code concise and reusable.


Example 3: Custom Hook for Window Size Detection

Sometimes, you may need to detect changes in the window size for responsive design or other UI adjustments. This custom hook keeps track of the window dimensions.

Creating the useWindowSize Hook

import { useState, useEffect } from 'react';

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); // Cleanup on unmount
  }, []);

  return windowSize;
}

export default useWindowSize;

In this example:

  • useWindowSize tracks width and height of the window.
  • It updates the dimensions whenever the window is resized, with an effect that cleans up the event listener on component unmount.

Using useWindowSize in a Component

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

function ResponsiveComponent() {
  const { width, height } = useWindowSize();

  return (
    <div>
      <p>Window width: {width}px</p>
      <p>Window height: {height}px</p>
    </div>
  );
}

This component displays the current window dimensions using the useWindowSize hook, which automatically updates on resize.


Tips for Creating Effective Custom Hooks

  1. Keep Hooks Focused: Each custom hook should ideally manage one specific responsibility, like data fetching or form handling.
  2. Use Built-in Hooks: Inside a custom hook, you can use other built-in hooks such as useState or useEffect to manage state and side effects.
  3. Start with "use": Always start custom hook names with “use” to follow React’s naming convention, which helps React identify them as hooks.
  4. Return Only Needed Data: Avoid returning unnecessary data or functions from custom hooks. This keeps the hook API simple and prevents unnecessary re-renders.

Conclusion

In this lesson, we covered:

  • The purpose and benefits of custom hooks
  • How to create and use custom hooks to encapsulate and reuse component logic
  • Examples of custom hooks for data fetching, form handling, and window size detection

Custom hooks empower you to extract and share logic across components efficiently, making your code more readable and maintainable. In the next module, we’ll explore React Router to learn how to manage navigation and routing in React applications.