Building our own hooks in React

Nitish Sharma
5 min readDec 11, 2022

--

If you’re familiar with React and have worked with it, you must have heard about Hooks. Hooks were introduced with React 16.8 in early 2019. They let us use states and other React features without writing a class. Which basically means it works with functional components.

From the title you must have guessed that in this article we’re going to understand how to write our own custom hooks in react, and that’s why you’re here. But before moving on to understand why we need our own custom hooks and how to build that, let’s first refresh our understanding of hooks and try to understand about the motivation behind introducing hooks in React.

What are hooks?

Hooks are nothing but some javascript functions which let us use React state and other lifecycle features from functional components in React. They are javascript function but they impose two additional ruled:

  1. Hooks can only be called at top level. It shouldn’t be called inside loops, conditions or nested functions.
  2. Hooks work only with functional components. It shouldn’t be called from regular javascript functions or from within a class based component.

Why are hooks useful?

  1. One major motivation behind hooks was to reuse stateful logic between components. Hooks allow us to reuse stateful logic without changing our component hierarchy. We will see how.
  2. Hooks let us split one component into smaller functions based on what pieces are related.
  3. Hooks let us use more of React’s features without classes. It makes functional components more powerful by enabling them to manage states.

Now that we know what hooks are and how hooks are useful, let’s try to see it in action.

Let’s say we’re working on a game which can be played using dice. We will definitely need to maintain some state of the game right? For example the score of the player, winning status and of course the dice value and a way to roll the dice so that we can play the game. We can use hooks for this.

export const Game = () => {
const [score, setScore] = useState(0);
const [isWon, setIsWon] = useState(false);

const [diceValue, setDiceValue] = useState(0);

const rollDice = (event) => {
event.preventDefault();
const newDiceValue = Math.floor(Math.random() * 6) + 1;
setDiceValue(newDiceValue);
};

useEffect(() => {
const newScore = score + diceValue;
setScore(newScore);
setIsWon(checkGameStatus(newScore));
}, [diceValue]);

const checkGameStatus = (score) => {
// some logic to decide the winning status
}

const reset = (event) => {
event.preventDefault();
setScore(0);
setIsWon(false);
}

return (
// some JSX to render the component
);
}

We can see how we’re able to manage multiple states for our Game components with the help of hooks without writing any classes, only with a couple of functions.

Here I’m not going to explain the use of every hook in this example. I assume that you understand this code and will try to drive the discussion towards building our own hook.

In this example we can see that hooks are working fine. But what if we need to extend our project? Suppose we’ve to create multiple games as part of the project which can also be played using dice and the game which we created is just one of those. We can create another game component in the same way. Let’s say we wrote one more game component AnotherComponent like this.

export const AnotherGame = () => {
const [score, setScore] = useState(0);
const [step, setStep] = useState(0);
const [isWon, setIsWon] = useState(false);

const [diceValue, setDiceValue] = useState(0);

const rollDice = (event) => {
event.preventDefault();
const newDiceValue = Math.floor(Math.random() * 6) + 1;
setDiceValue(newDiceValue);
};

useEffect(() => {
const newScore = score + diceValue;
const newStep = step + 1;
setScore(newScore);
setStep(newStep);
setIsWon(checkGameStatus(newScore));
}, [diceValue]);

const checkGameStatus = (score) => {
// some other logic to decide the winning status
}

const reset = (event) => {
event.preventDefault();
setScore(0);
setStep(0);
setIsWon(false);
}

return (
// some JSX to render the component
);
}

Let’s observe the two components. I am sure you must see a lot of common code in both the components. Here scores and winning status are something which can be very specific to the game and can vary based on different game types (of course for this example they look very similar). But don’t you think the dice value and the logic to roll the dice is going to be the same for all games which need a dice to play. Yes. And here comes the opportunity to make this code a little bit cleaner and to extract some common logic from both the components.

Let’s extract the common code to maintain dice value and logic to roll the dice into a separate function and name it as useDice”.

export const useDice = () => {
const [diceValue, setDiceValue] = useState(0);

const rollDice = (event) => {
event.preventDefault();
const newDiceValue = Math.floor(Math.random() * 6) + 1;
setDiceValue(newDiceValue);
};

return [diceValue, rollDice]
}

Now let’s remove the rollDice() function from our game components and make use of the useDice function to maintain dice value instead of useState. Like this (Commented part of code will get removed)

export const Game = () => {
const [score, setScore] = useState(0);
const [isWon, setIsWon] = useState(false);

const [diceValue, rollDice] = useDice(); // using our own hook useDice

/*
const [diceValue, setDiceValue] = useState(0);

const rollDice = (event) => {
event.preventDefault();
const newDiceValue = Math.floor(Math.random() * 6) + 1;
setDiceValue(newDiceValue);
};
*/

...
}
export const AnotherGame = () => {
const [score, setScore] = useState(0);
const [step, setStep] = useState(0);
const [isWon, setIsWon] = useState(false);

const [diceValue, rollDice] = useDice(); // using our own hook useDice

/*
const [diceValue, setDiceValue] = useState(0);

const rollDice = (event) => {
event.preventDefault();
const newDiceValue = Math.floor(Math.random() * 6) + 1;
setDiceValue(newDiceValue);
};
*/

...
}

You can see how we extracted the common code between components and used it with the help of a common function useDice. And we can further use this function wherever we need to implement a dice.

Congratulations! You’ve just created your own custom hook and used it. “useDice” is nothing but a custom hook.

What are Custom hooks?

Custom hooks are more of a convention than a feature. If a function name starts with “use” and it calls other hooks, we call it a custom hook. And just like other hooks custom hooks can also be called multiple times within a component and they will be completely independent with isolated states.

Why do we need custom hooks?

When we need to share logic between two javascript functions, we extract it to a third function right? Here both components as well as hooks are functions, so this works for them too!

So, the major motivation in building our own custom hooks is to reuse some common stateful logic between components. It helps us extract component logic into reusable functions. We have seen this in our example as well.

I hope you were able to follow along and understand about custom hooks in react and how to use them. If yes, please let me know in the comments below. You can also have a look into this repo (Dice_Board) where I have created and used the same useDice hook in two separate game components.

Thank you!

--

--

Nitish Sharma
Nitish Sharma

Written by Nitish Sharma

Senior Consultant - Application Developer at Thoughtworks, India. Checkout nitishsharma.dev

No responses yet