DEV Community

Linas Spukas
Linas Spukas

Posted on

React Hooks: Creating Custom State Hook

React custom hooks is nothing more than a JavaScript function. It encapsulates components logic, can be imported, exported and reused all over the application. If you have a repetitive hooks logic in components, it might be a good idea to extract it into a separate function and reuse all over the application. Also, a custom hook function can call other React hooks if needed.

Take for example this very simple counter application with a useState hook. On every button click we updating the counter by incrementing or decrementing. Let's refactor and move its logic to a custom hook.

// App.jsx

function App() {
  const [counter, setCounter] = React.useState(0);

  const increment = () => setCounter(counter + 1);
  const decrement = () => setCounter(counter - 1);

  return (
    <>
      <h1>{counter}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </>
  );
}

To make a custom hook out of this component logic, we need to extract useState and handlers into a separate function and move it in a separate file. A custom hook name should start with a prefix use, that will indicate the usage of hooks inside it:

// useCounter.js
import React from 'react';

export function useCounter() {
  const [counter, setCounter] = React.useState(0);
  const increment = () => setCounter(counter + 1);
  const decrement = () => setCounter(counter - 1);

  return { counter, increment, decrement };
}

The state and logic of the counter are now encapsulated into the function. It should return the counter and handlers, responsible for state modifications. Also do not forget to export the function.
In a current example, the function returns an object with state values, but it is not restricted only to objects. As it is only a simple JavaScript function, we can return any data type. It could be aswell an array:

// ...
return [counter, increment, decrement];
//...

The useCounter custom hook can now be imported anywhere in the application. Each instance of the function will hold its own state. That means even if you invoke useCounter function in the same application, each instance of the counter will hold it's own state, and other instances will not be affected.

The final result will look like this:

import { useCounter } from './useCounter.js';

function App() {
  const { counter, increment, decrement } = useCounter();

  return (
    <>
      <h1>{counter}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </>
  );
}

Custom hooks can consist not only state logic, but also be responsible for authentication, data fetching, DOM manipulations. Before creating your own hook, have a look at usehooks.com for already built recipes, maybe you'll what you been looking for.

Top comments (5)

Collapse
 
djfranck profile image
DjFranck

Nice Article. Thanks.
Does the state inside the custom hook can lead to component re-rendering ?
If yes does it mean that when a state full custom hook is used inside a component, the state of the custom hook can be considered belonging to the component state ?

Collapse
 
jivkojelev91 profile image
JivkoJelev91

What if i wanna use more then once useCounter in same component? Nice article by the way.

Collapse
 
dannypule profile image
Danny Pule

You could give the counter objects a unique name.

Instead of destructuring:

const { counter, increment, decrement } = useCounter();
Enter fullscreen mode Exit fullscreen mode

Name the objects:

const cats = useCounter();
const dogs = useCounter();
Enter fullscreen mode Exit fullscreen mode

Then use them like this:

<>
      <h2>{cats.counter}</h2>
      <button onClick={cats.increment}>Increment</button>
      <button onClick={cats.decrement}>Decrement</button>

      <h2>{dogs.counter}</h2>
      <button onClick={dogs.increment}>Increment</button>
      <button onClick={dogs.decrement}>Decrement</button>
</>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
ccreusat profile image
Clément Creusat

Hi ! Just passing by in this old article :)

Instead of naming the objects

const cats = useCounter();
const dogs = useCounter();
Enter fullscreen mode Exit fullscreen mode

You can do that :

const { counter: catsCounter, increment: catsIncrement, decrement: catsDecrement } = useCounter();
const { counter: dogsCounter, increment: dogsIncrement, decrement: dogsDecrement } = useCounter();
Enter fullscreen mode Exit fullscreen mode

It is the another way to achieve this.

Bye :)

Collapse
 
negreanucalin profile image
Negreanu Calin

Plain and simple, love it