Creating Custom Hooks in React

nitinfab

Nitin Sharma

Posted on February 8, 2023

Creating Custom Hooks in React

React, a popular JavaScript library for creating user interfaces, initially depended on class-based components to maintain state. However, as more developers began to use React, it became evident that this method had several significant limitations.

One of the key concerns with classes is that while dealing with the state, developers often find themselves repeating code, making their codebase more complicated and difficult to maintain. 

Furthermore, using class-based components frequently resulted in extensive, bulky code that was difficult to understand and manage. In response to these difficulties, the React team created a new concept known as "Hooks".

React Hooks has radically transformed how a component manages state and lifecycle methods by simplifying the codebase and increasing code reusability. It's easy to utilize; just create a function component, then include a useState hook that receives an initial value and allows modification.

Similarly, you may create custom hooks with React. Custom hooks allow you to take component functionality and turn it into reusable functions.

In this post, we'll delve deep into the concept of custom Hooks.

Understanding the Basic Structure and Syntax of a Hook

React 16.8 had seen the introduction of hooks, which have subsequently gained popularity as a method of controlling state and side effects in functional components.

The basic structure of a hook in a function component starts with the word "use". For example, the built-in useState hook is used to manage the state in a functional component.

The useState hook takes an initial value as an argument and returns an array with two elements: the current state and a function to update it.

Let's define a useState hook that takes the initial value as 0, and on button click, it increments by one.

import React, { useState } from 'react';

function Example() {

 // Declare a new state variable, which we'll call "count"
 const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

In the above example, the useState hook is being used by the Example functional component to handle the state of a count variable. When the increment button is pressed, it increments the count state to 1 with the help of the setCount function.

When to Use Custom Hooks

As previously mentioned, a useState hook allows you to access and modify the state. Similarly, you may develop custom hooks based on your needs.

But why custom hooks? If you are replicating the same component functionality in various components, you should employ custom hooks. Otherwise, you do not need to create custom hooks if you are not duplicating component logic.

Custom Hooks allow you to abstract and isolate states and side effects, giving you more control and flexibility over certain components. This enables you to isolate a component's behavior and encapsulate it in a reusable function.

When determining whether to use a custom Hook, evaluate if the logic you're attempting to reuse is stateful or has side effects. Custom Hooks are ideally suited for stateful functionality that is applicable to numerous components and can be abstracted away from the layout and presentation of the component. 

Custom Hooks are useful for things like controlling the state of a form, retrieving data from an API, and handling authentication or subscriptions.

It's also important to keep in mind that overusing custom Hooks may make the codebase more difficult to understand and maintain. They must thus be utilized carefully.

Best Practices for Custom Hooks in React

The React team suggests the following recommended practices while creating and utilizing custom hooks:

  1. Hooks must always be specified within a function component and cannot be created within a class component.

  2. Hooks must be called at the top level: Calling hooks within loops, conditions, or nested functions makes state preservation challenging. As a result, it is best to call hooks at the top level.

  3. Begin naming the custom hooks with "use".

  4. Two components employing the same Hook cannot share the same state: When two components share a custom Hook, each has its own isolated state and effects. This implies that the state and effects included within the Hook are not shared by the two components, but rather that each component has its own instance of the state and effects.

Reusing Logic with Custom Hooks in React

Assume you wish to increment and decrement functionality in distinct components, but for that, you're simply repeating code to add this functionality in every component.

Instead, you may create a custom hook and reuse it in all of your components.

Here is an example:

import { useState } from 'react';

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

  function increment() {
    setCount(count + 1);
  }

  function decrement() {
    setCount(count - 1);
  }

  return { count, increment, decrement };
}

export default useCounter;
Enter fullscreen mode Exit fullscreen mode

In this case, we simply created a useCounter custom hook with a logic that increments or decrements the count by one. It is recommended by React team to begin naming the custom hook function with "use" when creating a custom hook.

Then, in a component like this, you can use this custom hook:

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

function MyComponent() {
  const { count, increment, decrement } = useCounter();

  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

export default MyComponent;
Enter fullscreen mode Exit fullscreen mode

Similarly, when appropriate, you may utilize the same custom hooks in all of your components.

Building a Complex Custom Hook

Now let's take a more complex example where we can send the API URL and based on that, it fetches data from an API and has methods for loading, refreshing, and updating the data so we can handle the different stages of data fetching cleanly:

import { useState, useEffect } from 'react';

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

  useEffect(() => {
    async function fetchData() {
      setLoading(true);
      setError(null);
      try {
        const response = await fetch(url);
        const data = await response.json();
        setData(data);
      } catch (err) {
        setError(err);
      }
      setLoading(false);
    }
    fetchData();
  }, [url]);

  async function update(newData) {
    setLoading(true);
    setError(null);
    try {
      const response = await fetch(url, {
        method: 'PUT',
        body: JSON.stringify(newData),
        headers: { 'Content-Type': 'application/json' },
      });
      const data = await response.json();
      setData(data);
    } catch (err) {
      setError(err);
    }
    setLoading(false);
  }

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

export default useAPI;
Enter fullscreen mode Exit fullscreen mode

Here we have named the function starting with "use" and then used useState & useEffect hooks for different data, loading, and error.

Later, based on the condition, we modify the state and return it.

Now, we can use this custom hook in a component like this:

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

function App() {
 const { data, loading, error, update } = useAPI('https://jsonplaceholder.typicode.com/todos/1');

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

 return (
   <div>
     <p>Title: {data.title}</p>
     <button onClick={() => update({
       completed: false,
       id: 1,
       title: "Hello",
       userId: 1
     })}>Update</button>
   </div>
 );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Here, we are using our custom components to call the JSONPlaceholder API and display our data as a result. Additionally, the data will be updated and shown when you press the button.

To put it simply, we can load the data here and even update it with new data using our custom hook.

Similarly, you can use third-party libraries such as the swr or @tanstack/react-query package, to fetch the data and manage the state, helping you to write less code and streamline the process. There are many more third-party custom hooks available to ease authentication, local storage management, animations, and much more.

Using these hooks you can easily extend your React code and add custom functionality to all your React components. However, React hooks only work inside components and it's recommended to split your app into several reusable components to not only leverage these hooks but also for easier code maintenance and testing.

To generate production-ready code directly from Figma and Adobe XD design files, you can use the Locofy.ai plugin. Even if you don't have any existing designs, the plugin offers drag-and-drop UI components to quickly create highly extensible web designs.

What's more, is that using the Auto Components feature of the Locofy plugin, you can get suggestions to convert your design elements into React components instantly with value and style props.

Hope you like it.

That's it - thanks.

You can also find my writing on Substack.

💖 💪 🙅 🚩
nitinfab
Nitin Sharma

Posted on February 8, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related