Article image State: Managing State in React: Managing State with Custom Hooks

9.13. State: Managing State in React: Managing State with Custom Hooks

Page 37 | Listen in audio

Managing state is a fundamental aspect of building applications with React, as it allows components to maintain and update their data dynamically. In React, state refers to the data that a component holds and can change over time. While React provides built-in state management tools like the useState and useReducer hooks, there are scenarios where custom solutions are more appropriate. Custom hooks, introduced in React 16.8, offer a powerful way to encapsulate and reuse stateful logic across components. This section delves into managing state with custom hooks, providing a comprehensive guide for beginners.

Custom hooks are JavaScript functions that leverage React's built-in hooks to manage stateful logic. They enable developers to abstract and encapsulate complex state management logic into reusable functions. By using custom hooks, developers can avoid duplicating logic across multiple components, leading to cleaner and more maintainable code.

To create a custom hook, you simply define a function that uses React hooks like useState, useEffect, or useReducer to manage state. The function name should start with use to adhere to React's hook naming convention. This convention also helps React identify hook usage within the function, ensuring the rules of hooks are followed.

Let's start by exploring a simple example of a custom hook. Consider a scenario where multiple components need to manage a counter. Instead of duplicating the counter logic in each component, we can create a custom hook called useCounter:


import { useState } from 'react';

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

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

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

In this example, the useCounter hook manages the state of a counter, providing functions to increment, decrement, and reset the count. By using this custom hook, any component can easily integrate counter functionality without duplicating code:


function CounterComponent() {
  const { count, increment, decrement, reset } = useCounter(10);

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

Custom hooks can also manage more complex state and side effects. For instance, consider a scenario where you need to fetch data from an API and manage the loading and error states. A custom hook can encapsulate this logic, providing a clean and reusable solution:


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('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, [url]);

  return { data, loading, error };
}

With the useFetch hook, components can effortlessly handle data fetching logic:


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

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

  return (
    <ul>
      {data.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

Custom hooks can also be parameterized, allowing them to be more flexible and adaptable to different use cases. The useCounter example already demonstrates parameterization by accepting an initial value for the counter. Similarly, the useFetch hook can be further enhanced to accept additional options like request headers or query parameters.

Another common use case for custom hooks is form handling. Managing form state, validation, and submission can be complex, especially in larger applications. A custom hook can simplify this process by abstracting the form logic. Here's an example of a custom hook for managing form state:


import { useState } from 'react';

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

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

  const resetForm = () => setValues(initialValues);

  return { values, handleChange, resetForm };
}

This useForm hook manages form values and provides a function to handle input changes. Components can use this hook to streamline form handling:


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

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

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

Custom hooks can also be composed, meaning you can create hooks that use other hooks to build more complex functionality. For example, you might create a useAuth hook that internally uses useFetch to authenticate users and manage authentication state. This composition allows you to build powerful abstractions tailored to your application's needs.

While custom hooks provide a powerful way to manage state, it's essential to use them judiciously. Overusing custom hooks or creating overly complex hooks can lead to code that's difficult to understand and maintain. Always strive for simplicity and clarity, and ensure that custom hooks are well-documented and tested.

In summary, custom hooks are a versatile tool for managing state in React applications. They allow developers to encapsulate and reuse stateful logic, leading to cleaner and more maintainable code. By understanding how to create and use custom hooks, you can build more efficient and scalable React applications. Whether you're managing a simple counter or handling complex form logic, custom hooks offer a flexible solution that can adapt to your specific needs.

Now answer the exercise about the content:

What is a key benefit of using custom hooks in React applications?

You are right! Congratulations, now go to the next page

You missed! Try again.

Article image State: Managing State in React: Using State in Concurrent Mode

Next page of the Free Ebook:

38State: Managing State in React: Using State in Concurrent Mode

7 minutes

Earn your Certificate for this Course for Free! by downloading the Cursa app and reading the ebook there. Available on Google Play or App Store!

Get it on Google Play Get it on App Store

+ 6.5 million
students

Free and Valid
Certificate with QR Code

48 thousand free
exercises

4.8/5 rating in
app stores

Free courses in
video, audio and text