DEV Community

loading...
Cover image for Few React hooks useful in any project

Few React hooks useful in any project

soorajdewarists profile image Suraj Kumar Nanda ・4 min read

Alt Text
In this article, I will cover five simple React hooks that you will find handy in any project. These hooks are useful no matter the features of the application.

→ useModalState

Web applications use modals extensively and for various reasons. When working with modals, you quickly realize that managing their state is a tedious and repetitive task. And when you have code that’s repetitive and tedious, you should take time to abstract it. That’s what useModalState does for managing modal states.
Many libraries provide their version of this hook.

The implementation of the hook is very simple, even trivial. But in my experience, it pays off using it rather than rewriting the code for managing the modal’s state each time.

import React from "react";
import Modal from "./Modal";

export const useModalState = ({ initialOpen = false } = {}) => {
  const [isOpen, setIsOpen] = useState(initialOpen);

  const onOpen = () => {
    setIsOpen(true);
  };

  const onClose = () => {
    setIsOpen(false);
  };

  const onToggle = () => {
    setIsOpen(!isOpen);
  };

  return { onOpen, onClose, isOpen, onToggle };
};
Enter fullscreen mode Exit fullscreen mode

→ useConfirmationDialog

useConfirmationDialog is another modal-related hook that I use quite often. It's a common practice to ask users for confirmations when performing sensitive actions, like deleting records. So it makes sense to abstract that logic with a hook. Here's a sample implementation of the useConfirmationDialog hook:

import React, { useCallback, useState } from 'react';
import ConfirmationDialog from 'components/global/ConfirmationDialog';

export default function useConfirmationDialog({
    headerText,
    bodyText,
    confirmationButtonText,
    onConfirmClick,
}) {
    const [isOpen, setIsOpen] = useState(false);

    const onOpen = () => {
        setIsOpen(true);
    };

    const Dialog = useCallback(
        () => (
            <ConfirmationDialog
                headerText={headerText}
                bodyText={bodyText}
                isOpen={isOpen}
                onConfirmClick={onConfirmClick}
                onCancelClick={() => setIsOpen(false)}
                confirmationButtonText={confirmationButtonText}
            />
        ),
        [isOpen]
    );

    return {
        Dialog,
        onOpen,
    };
}
Enter fullscreen mode Exit fullscreen mode

One thing to note here is that this implementation works fine as long as your confirmation modal doesn’t have any controlled input elements. If you do have controlled inputs, it’s best to create a separate component for your modal. That’s because you don’t want the content of the modal, including those inputs, to re-render each time the user types something.

→ useAsync

Properly handling async actions in your application is trickier than it seems at first. There are multiple state variables that you need to keep track of while the task is running. You want to keep the user informed that the action is processing by displaying a spinner. Also, you need to handle the errors and provide useful feedback when they happen. So it pays off to have an established framework for dealing with async tasks in your React project. And that’s where you might find useAsync useful. Here's an implementation of the useAsync hook:

export const useAsync = ({ asyncFunction }) => {
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [result, setResult] = useState(null);

  const execute = useCallback(
    async (...params) => {
      try {
        setLoading(true);
        const response = await asyncFunction(...params);
        setResult(response);
      } catch (e) {
        setError(e);
      }
      setLoading(false);
    },
    [asyncFunction]
  );

  return { error, result, loading, execute };
};
Enter fullscreen mode Exit fullscreen mode

The hook is not hard to write yourself and that’s what I often do. But it might make sense for you to use a more mature library implementation instead.

→ useTrackErrors
Form validation is another part of React applications that people often find tedious. With that said, there are plenty of great libraries to help with forms management in React. One great alternative is formik. However, each of those libraries has a learning curve. And that learning curve often makes it not worth using in smaller projects. Particularly if you have others working with you and they are not familiar with those libraries.
But it doesn’t mean we can’t have simple abstractions for some of the code we often use. One such piece of code that I like to abstract is error validation. Checking forms before submitting to API and displaying validation results to the user is a must-have for any web application. Here’s an implementation of a simple useTrackErrors hook that can help with that:

import React, { useState } from "react";
import FormControl from "./FormControl";
import Input from "./Input";
import onSignup from "./SignupAPI";

export const useTrackErrors = () => {
  const [errors, setErrors] = useState({});

  const setErrors = (errsArray) => {
    const newErrors = { ...errors };
    errsArray.forEach(({ key, value }) => {
      newErrors[key] = value;
    });

    setErrors(newErrors);
  };

  const clearErrors = () => {
    setErrors({});
  };

  return { errors, setErrors, clearErrors };
};
Enter fullscreen mode Exit fullscreen mode

→ useDebounce

Debouncing has a broad use in any application. The most common use is throttling expensive operations. For example, preventing the application from calling the search API every time the user presses a key and letting the user finish before calling it. The useDebounce hook makes throttling such expensive operations easy. Here's a simple implementation that’s written using AwesomeDebounceLibrary under the hood:

import AwesomeDebouncePromise from "awesome-debounce-promise";

const debounceAction = (actionFunc, delay) =>
  AwesomeDebouncePromise(actionFunc, delay);

function useDebounce(func, delay) {
  const debouncedFunction = useMemo(() => debounceAction(func, delay), [
    delay,
    func,
  ]);

  return debouncedFunction;
}
Enter fullscreen mode Exit fullscreen mode

One thing to note with this implementation: You need to ensure that the expensive function is not recreated on each render. Because that will reset the debounced version of that function and wipe out its inner state. There are two ways to achieve that:
Declare the expensive function outside of the functional component (like in the code example).
Wrap the expensive function with useCallback hook.
And that’s it for this post. There are many useful hook libraries worth checking out, and if you’re interested, ping me on this.

Discussion (0)

pic
Editor guide