5 React Hooks to use and why

Boost your team

Proxify developers are a powerful extension of your team, consistently delivering expert solutions. With a proven track record across 500+ industries, our specialists integrate seamlessly into your projects, helping you fast-track your roadmap and drive lasting success.

Find a developer

React Hooks let you use state and other React features without writing a class.

Before they were introduced, in order to have a component that holds a state of some sort, you had to write a class component. Now with the help of useState, useEffect, useRef, and other very useful built-in React Hooks you can write your whole application using a purely functional approach! Isn’t that neat?

I think it is! But there’s more: along with built-in hooks, there came a functionality to build your own custom hooks, which can do basically anything you need.

In this article, I’d like to show you a few simple examples of custom hooks, which may make your life much easier.

Hook no. 1: useUpdateEffect

First and probably my favorite custom hook is called useUpdateEffect. It behaves very similarly to useEffect but there’s a big difference. See, built-in useEffect executes every time a property in the dependency array changes, but also on the initial render of the component.

Sometimes we want to prevent that. For example, to reduce those pesky rerenders that slow down our app. Or maybe you don’t need to fetch API data on each rerender, but only when certain state changes? Here’s a solution for that:

We’re basically making use of useRef to keep track of isInitialMount ref and checking it in the useEffect. We can pass any callback function to our custom hook along with a dependency array.

import { useEffect, useRef } from 'react';

export default function useUpdateEffect(effect: Function, dependencyArray: Array<any> = []) {
 const isInitialMount = useRef(true);

 useEffect(() => {
   if (isInitialMount.current) {
     isInitialMount.current = false;
   } else {
     return effect();
   }
 }, dependencyArray);
}

Here is a simple use case for that hook – we have a component that displays a value, and when this value changes, it alerts a user. If we were to use regular useEffect there, an alert dialog would pop up every time component is rerendered. Now it’s only when the value changes. Neat!

import { useUpdateEffect } from 'hooks/useUpdateEffect';

export const myComponent = (props) => {
 const { value } = props;

 useUpdateEffect(() => {
   alert(`Value has changed to: ${value}`);
 }, [value]);

 return ( <span> {value} </span> );
};

The next hook is also a simple one. It comes in handy in many situations where we would like to know what was that previous value of state in our component.

In class-based components, we had a lifecycle method componentDidUpdate which provided us with the previous state. In order to have that functionality in functional components, we can build our own custom hook to handle this scenario for us.

Boost your team

Proxify developers are a powerful extension of your team, consistently delivering expert solutions. With a proven track record across 500+ industries, our specialists integrate seamlessly into your projects, helping you fast-track your roadmap and drive lasting success.

Find a developer

Hook no. 2: usePrevious hook

import { useRef } from "react"

export const usePrevious = (value) => {
 const currentRef = useRef(value)
 const prevRef = useRef()

 if (currentRef.current !== value) {
   prevRef.current = currentRef.current
   currentRef.current = value
 }
 return prevRef.current
}

There are certain situations that you can encounter in app development where you need to know what was the previous state value. This is a scenario when usePrevious hook comes in handy.

You just need to define a state as usual, like this:

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

Then assign this state value to an usePrevious hook and bam! You now have access to the old count value!

const previousCount = usePrevious(count)

Hook no. 3: useTimeout

This hook is very straightforward. It bears no difference to the regular setTimeout method from vanilla Javascript. But this custom hook simplifies its usage, and most importantly – lets you forget about clearing the interval when you’re done using it.

The behavior is super simple – you call useTimeout, with a callback method as a first parameter and you put the time in milliseconds as a second parameter. The rest is done for you.

You will no longer get this annoying error saying you “[...]can’t perform a React state update on an unmounted component[...]” when you accidentally forget to execute clearTimeout in the cleanup part of useEffect.

import { useCallback, useEffect, useRef } from 'react';

export const useTimeout = (callback, timeoutDelay) => {
 const callbackRef = useRef(callback);
 const timeoutRef = useRef();

 useEffect(() => {
   callbackRef.current = callback;
 }, [callback]);

 const set = useCallback(() => {
   timeoutRef.current = setTimeout(() => callbackRef.current(), timeoutDelay);
 }, [timeoutDelay]);

 const clear = useCallback(() => {
   timeoutRef.current && clearTimeout(timeoutRef.current);
 }, []);

 useEffect(() => {
   set();
   return clear;
 }, [timeoutDelay, set, clear]);

 const reset = useCallback(() => {
   clear();
   set();
 }, [clear, set]);

 return { reset, clear };
};

Hook no. 4: useDebounce

This hook may come in handy whenever you’re trying to implement search functionality to your application.

For example, you’re trying to call an API that will return a search result, based on your text input. But making an API call each time you press a key on your keyboard can cause some trouble. When you’re searching for a specific keyword you can actually wait until a user finishes typing, or at least he stops for a certain amount of time. That’s where debouncing comes in handy.

The term “debounce” comes from the electronics field – it’s a process that happens when you’re pressing a button, for example, let’s say on your TV remote, the signal travels to the microcontroller of the remote so quickly that before you release the button, it bounces, and the processor registers your click multiple times.

To combat this issue, the microcontroller stops registering any actions for fraction of a second, to prevent sending multiple signals for nothing.

The same thing applies to the JavaScript world. A search box example shows us that we would like to call a certain method only once per set time.

Implementation:

In this case, we’re going to perform a “hookception”. We’re going to use our own custom hook to build another custom hook.

With useTimeout at our disposal, it’s super simple - we just need to get our reset and clear methods, set our timeout with a callback function, and set delay, coming from useDebounce method parameters, and do the clean up afterward.

import useTimeout from 'hooks/useTimeout';

export const useDebounce = (callback, delay, dependencies) => {
 const { reset, clear } = useTimeout(callback, delay);
 useEffect(reset, [...dependencies, reset]);
 useEffect(clear, []);
};

Hook no. 5: useStorage

During your frontend development career, you either already have or will stumble upon browser storage. Local storage or session storage. They both have their use cases, advantages, and limitations.

Our next hook implementation will simplify the interaction with both types of storage. Values are stored as key -> value mapping in JSON format. It simplifies getting and saving values.

Our implementation will take care of fetching values from storage and removing them. This hook returns an array with storage value, a method for storing the value, and a function responsible for removing the value from storage.

import { useCallback, useState, useEffect } from 'react';

export const useLocalStorage = (key, value) => useStorage(key, value, window.localStorage);

export const useSessionStorage = (key, value) => {
 return useStorage(key, value, window.sessionStorage);
}

const useStorage = (key, value, storageObject) => {
 const [storageValue, setStorageValue] = useState(() => {
   const jsonValue = storageObject.getItem(key);
   if (jsonValue != null) return JSON.parse(jsonValue);

   if (typeof value === 'function') {
     return value();
   }
   return value;
 });

 useEffect(() => {
   if (storageValue === undefined) return storageObject.removeItem(key);
   storageObject.setItem(key, JSON.stringify(storageValue));
 }, [key, storageValue, storageObject]);

 const remove = useCallback(() => {
   setStorageValue(undefined);
 }, []);

 return [storageValue, setStorageValue, remove];
}

To use that hook we just need to call it and destructure returned values and methods.

const [user, setUser, removeUser] = useSessionStorage("user", "John Doe")

Custom hooks allow us to abstract certain parts of our application to make our code cleaner, easier to read and reuse the logic in our application.

Business logic is also a good candidate to be put into custom hooks. Not only makes our code easier to maintain – having only one place to check what’s wrong with the logic is better to have it spread out through 10 components. But it also brings us closer to having pure, reusable components as a base of our app. There is no longer a need to have specialized components which deal with our business logic.


Contribution by Jacek Paciorek

GitHub

Portfolio

LinkedIn

Jacek Paciorek

Jacek Paciorek

Fullstack (FE-heavy)

6 years of experience

Expert in React.js

Jacek is a dedicated fullstack software developer with several years of commercial experience, excelling in web and mobile development. Jacek’s career spans diverse industries, including insurance, entertainment, art, and healthcare, with notable roles at CERN and contributions to projects across Poland, France, and Switzerland. Outside of work, Jacek enjoys skiing, sailing, woodworking, tinkering, and has a particular passion for cars—whether driving, fixing, modifying, or detailing them.

Verified author

We work exclusively with top-tier professionals.
Our writers and reviewers are carefully vetted industry experts from the Proxify network who ensure every piece of content is precise, relevant, and rooted in deep expertise.

Find your next developer within days, not months

In a short 25-minute call, we would like to:

  • Understand your development needs
  • Explain our process to match you with qualified, vetted developers from our network
  • You are presented the right candidates 2 days in average after we talk

Not sure where to start? Let’s have a chat