5 React Hooks to use and why

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.

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

Find your next developer within days, not months

We can help you deliver your product faster with an experienced remote developer. All from €31.90/hour. Only pay if you’re happy with your first week.

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
  • Share next steps to finding the right match, often within less than a week

Not sure where to start?

Let’s have a chat

First developer starts within days. No aggressive sales pitch.