Menu

Mastering Custom React Hooks: Simplify State Management and API Calls

Learn how to create and implement custom React hooks to streamline your state management and API calls. This comprehensive guide covers hook basics, best practices, and real-world examples for efficient React development.

Nikhil Singh

5 min read
Mastering Custom React Hooks: Simplify State Management and API Calls

React Hooks have revolutionized the way we manage state and side effects in functional components. While React provides several built-in hooks, creating custom hooks can significantly enhance your development experience by encapsulating complex logic and promoting code reuse. In this guide, we'll explore how to create custom React hooks for state management and API calls, two common challenges in React development.

Understanding Custom React Hooks

Custom hooks are JavaScript functions that start with "use" and may call other hooks. They allow you to extract component logic into reusable functions, making your code cleaner and more modular.

Creating a Custom Hook for State Management

Let's start by creating a custom hook for managing a toggle state, which is a common UI pattern.

import { useState, useCallback } from 'react';

function useToggle(initialState = false) {
  const [state, setState] = useState(initialState);

  const toggle = useCallback(() => {
    setState(prevState => !prevState);
  }, []);

  return [state, toggle];
}

export default useToggle;

Using this custom hook in a component:

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

function ToggleComponent() {
  const [isOn, toggleIsOn] = useToggle();

  return (
    <button onClick={toggleIsOn}>
      {isOn ? 'ON' : 'OFF'}
    </button>
  );
}

export default ToggleComponent;

Custom Hook for API Calls

Now, let's create a more complex custom hook for handling API calls. This hook will manage loading states, errors, and data fetching.

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    async function fetchData() {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    }

    fetchData();
  }, [url]);

  return { data, loading, error };
}

export default useFetch;

Using the useFetch hook in a component:

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

function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`https://api.example.com/users/${userId}`);

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

  return (
    <div>
      <h1>{user.name}</h1>
      <p>Email: {user.email}</p>
    </div>
  );
}

export default UserProfile;

Advanced Custom Hook: Combining State Management and API Calls

Let's create a more advanced custom hook that combines state management with API calls, perfect for handling form submissions.

import { useState, useCallback } from 'react';

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

  const submit = useCallback(async (formData) => {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(formData),
      });
      if (!response.ok) {
        throw new Error('Submission failed');
      }
      const result = await response.json();
      setData(result);
      return result;
    } catch (err) {
      setError(err);
      throw err;
    } finally {
      setLoading(false);
    }
  }, [url]);

  return { submit, data, loading, error };
}

export default useSubmit;

Using the useSubmit hook in a form component:

import React, { useState } from 'react';
import useSubmit from './useSubmit';

function RegistrationForm() {
  const [username, setUsername] = useState('');
  const [email, setEmail] = useState('');
  const { submit, loading, error } = useSubmit('https://api.example.com/register');

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await submit({ username, email });
      alert('Registration successful!');
    } catch (err) {
      // Error is already set in the hook
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        placeholder="Username"
        required
      />
      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Submitting...' : 'Register'}
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}

export default RegistrationForm;

Best Practices for Custom Hooks

  1. Keep it simple: Custom hooks should focus on a single piece of functionality.
  2. Use the "use" prefix: Always start your custom hook names with "use" to follow React conventions.
  3. Avoid premature abstraction: Create custom hooks when you see repeated logic across components, not preemptively.
  4. Handle cleanup: If your hook sets up any subscriptions or timers, make sure to clean them up to avoid memory leaks.
  5. Test your hooks: Use the @testing-library/react-hooks package to unit test your custom hooks.

Conclusion

Custom React hooks are a powerful tool for creating reusable logic in your React applications. By extracting complex state management and API call logic into custom hooks, you can significantly simplify your components and make your code more maintainable.

As you continue to work with React, look for opportunities to create custom hooks. They can help you DRY up your code, make your components more focused, and create a library of reusable functionality specific to your application's needs.

Remember, the key to mastering custom hooks is practice. Start by identifying repeated patterns in your components and gradually refactor them into custom hooks. With time, you'll develop an intuition for when and how to create custom hooks that can dramatically improve your React development workflow.

Happy coding!

Level Up Your UI for FREE

An open-source collection of beautiful animated components

Explore Components