DEV Community

Cover image for Implementing React Custom Hooks: A Complete Guide
Suresh Mohan for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

Implementing React Custom Hooks: A Complete Guide

React Hooks are functions that allow you to use state and life-cycle features from function components. If you’ve been using React, you’ve probably used their built-in Hooks, such as useState, useEffect, and useContext. However, React does not limit you to using only those Hooks.

Creating your own Hooks in React can be useful in various situations. Custom Hooks are an excellent way to share logic between components and improve your component tree’s readability.

This article explains how to implement various custom Hooks in React. Let’s get started.

Implementing a custom Hook

A custom Hook is a JavaScript function that begins with use. It is not mandatory to begin the custom Hook name with “use,” but without it, React would be unable to check for violations of the Hooks rules automatically. Therefore, it is critical to adhere to that naming convention.

Consider the following example of defining a custom Hook called useFriendStatus.

import { useState, useEffect } from 'react';

function useFriendStatus(friendID) {  
  const \[isOnline, setIsOnline\] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we call the useState and useEffect Hooks unconditionally at the top level of our custom Hook, just as we would in a component. The useFriendStatus Hook’s purpose is to subscribe us to a friend’s status. As a result, it accepts a friendID as an argument and returns whether the friend is online.

If we didn’t define the useFriendStatus custom Hook, we’d have to write the logic inside this Hook in every component to subscribe to a friend’s status. So, instead of duplicating code, we can use this custom Hook.

Now, let’s look at how we can put this custom Hook to use.

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}
export default FriendStatus;
function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);
  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}
export default FriendListItem;
Enter fullscreen mode Exit fullscreen mode

In the above example, you can see that the useFriendStatus Hook is used in two different functional components, FriendStatus and FriendListItem. You can even use the same custom Hook twice within the same component.

Note: The state is not shared between two components that use the same Hook.

Passing information between Hooks

Since Hooks are a type of function, we can pass data between them. Consider the following example where a user can select any friend and see whether that friend is online.

const friendList = \[
  { id: 1, name: 'John' },
  { id: 2, name: 'Michael' },
  { id: 3, name: 'Mary' },
\];

function ChatRecipientPicker() {
  const \[recipientID, setRecipientID\] = useState(1);  
  const isRecipientOnline = useFriendStatus(recipientID);
  return (
    <>
      <Circle color={isRecipientOnline ? 'green' : 'red'} />
      <select
        value={recipientID}
        onChange={e => setRecipientID(Number(e.target.value))}
      >
        {friendList.map(friend => (
          <option key={friend.id} value={friend.id}>
            {friend.name}
          </option>
        ))}
      </select>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here, the recipientID state variable value is passed as an argument to the custom useFriendStatus Hook. When the user selects a different friend in the picker, we use the useState Hook to update the recipientID. The useFriendStatus Hook then unsubscribes from the previously selected friend and subscribes to the newly selected friend’s status.

Custom Hooks examples

Now that you have an understanding of how custom Hooks work, let’s look at several scenarios for creating custom Hooks.

useLocalStorage

To use local storage in React, we can construct a custom Hook. The Hook allows you to preserve data in the browser as key-value pairs for later use.

import { useState } from "react";
export default function App() {
  // Usage
  const \[name, setName\] = useLocalStorage("name", "John");
  return (
    <div>
      <input
        type="text"
        placeholder="Enter your name"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
    </div>
  );
}
// Hook
function useLocalStorage(key, initialValue) {
  const \[storedValue, setStoredValue\] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });
const setValue = (value) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key,JSON.stringify(valueToStore));
    } catch (error) {
      console.log(error);
    }
  };
  return \[storedValue, setValue\];
}
Enter fullscreen mode Exit fullscreen mode

useToggle

This is a simple but helpful custom Hook that can be used effectively in many web applications. The useToggle Hook allows you to take a true or false value and convert it to the inverse. It can be used to toggle modals and menus.

import { useCallback, useState } from 'react';
export default function App() {
    // Usage
    const \[isModalOpen, setIsModalOpen\] = useToggle();
    return (
        <button onClick={setIsModalOpen}>
          {isModalOpen ? 'Close Modal' : 'Open Modal'} 
        </button>
    );
}
// Hook
const useToggle = (initialState = false) => {
    const \[state, setState\] = useState(initialState);
    const toggle = useCallback(() => setState(state => !state), \[\]);
    return \[state, toggle\]
}
Enter fullscreen mode Exit fullscreen mode

useEventListener

You may find yourself adding a lot of event listeners using useEffect in your code. In such situations, you should move that logic to a custom Hook. The useEventListener Hook first ensures that the element supports the addEventListener method, and then it creates and adds an event listener. If eventName or element changes, the Hook will rerun.

import { useState, useRef, useEffect, useCallback } from "react";
export default function App() {
  const \[coords, setCoords\] = useState({ x: 0, y: 0 });
  const handler = useCallback(
    ({ clientX, clientY }) => {
      setCoords({ x: clientX, y: clientY });
    },
    \[setCoords\]
  );
  // Usage
  useEventListener('mousemove', handler);
return (
    <h1>
      The mouse position is ({coords.x}, {coords.y})
    </h1>
  );
}
// Hook
function useEventListener(eventName, handler, element = window) {
  const savedHandler = useRef();
  useEffect(() => {
    savedHandler.current = handler;
  }, \[handler\]);
  useEffect(
    () => {
      const isSupported = element && element.addEventListener;
      if (!isSupported) return;
      const eventListener = event => savedHandler.current(event);
      element.addEventListener(eventName, eventListener);
      return () => {
        element.removeEventListener(eventName, eventListener);
      };
    },
    \[eventName, element\]
  );
}
Enter fullscreen mode Exit fullscreen mode

Similarly, you can write custom Hooks to handle various use cases, such as form handling, animation, timers, and many others.

Advantages of custom Hooks

The main advantage of React Hooks is the reusability of stateful logic. In addition, custom Hooks can be easily shared with other components without changing the component hierarchy. Also, we don’t have to be concerned about the this keyword when using Hooks.

Compared to HOCs, the custom Hooks approach provides cleaner logic and a better understanding of the relationship between the data and the component. As a result, it’s easier to read and has fewer lines of code.

We can write separate unit tests for custom Hooks, making testing easier. It is also easier to test components with multiple custom Hooks using the React Testing Library.

Conclusion

Custom Hooks enable exceptional flexibility in sharing logic previously unavailable in React components. I hope this guide has given you a better understanding of creating your own React Hooks.

Thank you for reading.

The Syncfusion React JS suite offers over 65 high-performance, lightweight, modular, and responsive UI components in a single package. This library is the only suite you’ll ever need to construct a complete application.

If you have any questions or comments, you can contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!

Related blogs

Top comments (0)