React hooks simplify the process of creating reusable, clean and versatile code, and advanced optimization techniques like memoization are now more accessible and easier to use. React’s official documentation doesn’t cover custom hooks in detail as it covers the basic hooks, so the focus of this article is going to be primarily on building custom React hooks and best practices.
Understanding basic React hooks are required to get the most out of this article. If you aren’t already familiar with the basics, there are numerous great articles out there covering them. For example, React’s official docs are a great place to start.
In order to build a versatile, performant and reusable custom hook, there are several things to keep in mind.
Since we’re working with functional components and hooks, we no longer have the need for lifecycle methods. Each time state or a prop is changed, the functional component is being re-rendered and, thusly, our custom hook is being called over and over again.
Basic React hooks are the core of any custom hook. We can use memoizationand hook dependency arrays to control which parts of our custom hook will change or will not change with each re-render. It’s important to understand the role each basic hook can have in our custom hook in order to use them effectively and build performant hooks.
There are a few important rules to keep in mind. These rules are explained in detail on React hooks documentation.
Now that we have covered the basics, we’re ready to build our own custom hook. In the following example, we’ll establish a solid pattern for building custom hooks and go through some of the best practices.
Let’s imagine that we’re working on a project where users can play multiple games that use dice rolls as part of their game mechanic. Some games require only a single dice to play and some games may require multiple dice to play. We’ll also assume that during some games, the number of dice used may change.
Keeping that in mind, we are going to build useGameDice hook with the following features:
We are declaring our custom hook as a regular arrow function using the recommended convention of naming custom hooks - the name should start with "use" keyword. We are also importing React hooks that we’ll use later on in our implementation. We could also import constants, other functions, other custom hooks, etc.
Our hook can be initialized with 2 optional variables:
Both variables have a default value of 1 to avoid any errors and simplify the hook setup.
import { useState, useMemo, useCallback, useEffect } from "react";export const useGameDice = (initialNumberOfDice = 1, initialDiceValue = 1) => {
/* We’ll be adding code here in order */
};
First, we need to set up our state. We’ll declare two simple states:
We are also initializing initialDiceState variable that creates the initial array value that will be assigned on initial render and state reset. This value is memoized to avoid array being initialized and filled with default values on each re-render.
const [diceValue, setDiceValue] = useState();
const [numberOfDice, setNumberOfDice] = useState(initialNumberOfDice);const initalDiceState = useMemo(
() => Array(numberOfDice).fill(initialDiceValue),
[numberOfDice, initialDiceValue]
);
Next, we’ll create the following functions:
const generateRandomDiceNumber = useCallback(() => {
return Math.floor(Math.random() * 6) + 1;
}, []);const rollDice = useCallback(() => {
const arrayConfig = { length: numberOfDice };
const newDiceValues = Array.from(arrayConfig, generateRandomDiceNumber);
setDiceValue(newDiceValues);
}, [numberOfDice, generateRandomDiceNumber]);const resetDice = useCallback(() => {
setDiceValue(initalDiceState);
}, [initalDiceState]);
We are using useCallback hook to control when the functions are going to be re-initialized. Functions are re-initialized only when any variable in their dependency array changes. In case of the generateRandomDiceNumberfunction, it’s never re-initialized after the first render and initialization because this function doesn’t depend on any external variable or state.
We need to set up a listener that will watch for updates to our initial dice state. This side effect has two responsibilities:
useEffect(() => {
setDiceValue(initalDiceState);
}, [initalDiceState]);
Finally, we are defining our state and api objects and returning them in an array, following the useState convention. Let’s take a look at each object:
const state = {
diceValue,
numberOfDice
};const api = useMemo(
() => ({
setNumberOfDice,
rollDice,
resetDice
}),
[setNumberOfDice, rollDice, resetDice]
);return [state, api];
We are returning the objects in an array because we want this hook to be flexible. By doing so, we allow developers to rename the returned variables and allow them to initialize multiple instances of this hook if needed.
const [diceFirst_state, diceFirst_api] = useGameDice();
const [diceSecond_state, diceSecond_api] = useGameDice();
You can see the final implementation and full code with a demo on the following GitHub repository.
By now, you might have noticed that we grouped the code we were adding in sections. This structured and clean pattern follows a logical path:
It’s no surprise that hooks were well received by React community. Developers are able to share logic between components more easily, create multiple components (interfaces) for each custom hook, pick and choose the parts of hook’s state and API they’ll use in their components, etc.
This reusability and versatility make hooks a real game-changer in React app development. With an established pattern and best practices when building custom React hooks, developers are able to deliver code with consistent quality, clear structure, and optimal performance.
Thanks for reading. If you liked this post, share it with all of your programming buddies!
Further reading
☞ React - The Complete Guide (incl Hooks, React Router, Redux)
☞ Modern React with Redux [2019 Update]
☞ How To Write Better Code in React
☞ React Router: Add the Power of Navigation
☞ Getting started with React Router
☞ Using React Router for optimizing React apps
☞ Creating RESTful APIs with NodeJS and MongoDB Tutorial
Originally published on medium.com
#reactjs #react-native #web-development