DEV Community

Cover image for Custom Hook - ReactJS
AbhishekkGautam
AbhishekkGautam

Posted on

Custom Hook - ReactJS

Hello πŸ‘‹, Hope you're doing well.
Before diving into the custom hook, let's revise some points about Hooks in React.

Hooks

  1. useState
  2. useEffect
  3. useContext
  4. useRef
  5. useMemo

and many more...

All of the above mentioned are in-built hooks in React. Most of us have used these hooks many times while working with functional components.

What are Hooks?

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

In simple words, Hooks are in-built functions which help React developers in managing state & lifecycle methods in a more clean & efficient way.

Rules of Hooks

  1. Don’t call hooks inside loops, conditions, or nested functions.
  2. Only call hooks from React functions.

You can read more about hooks from official docs - Hooks

All these in-built hooks are cool but what about creating our own custom hooks,
Is it possible?😯

YES!πŸ”₯

Let's create our own custom hook.
And we'll take the help of our legendary example - Counter App.

  1. Create a CounterOne.js file & write logic for increment, decrement & reset using in-built hook - useState.
import React, {useState} from 'react'

const CounterOne = () => {

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

  const increment = () => {
    setCount(count => count + 1)
  }

  const decrement = () => {
    setCount(count => count - 1)
  }

  const reset = () => {
    setCount(0)
  }

  return(
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

export default CounterOne
Enter fullscreen mode Exit fullscreen mode
  1. Import CounterOne.js in App.js
import CounterOne from "./CounterOne";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <CounterOne />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now we can increment, decrement & reset the counter.

counter app

What if we want one more counter - easy no?
We'll copy the code of CounterOne.js in CounterTwo.js & Import it in App.js.

import React, {useState} from 'react'

const CounterTwo = () => {

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

  const increment = () => {
    setCount(count => count + 1)
  }

  const decrement = () => {
    setCount(count => count - 1)
  }

  const reset = () => {
    setCount(0)
  }

  return(
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  )
}

export default CounterTwo
Enter fullscreen mode Exit fullscreen mode

Here we go. we have now two counters on the view.

Two counter app

But doing copy/paste of whole logic isn't a good practice. We should avoid repeating ourselves.

Now we'll take advantage of creating a custom hook & extract our logic in a separate file.

  1. Create a useCounter.js file.

we must prefix the custom hook's name with use.

  1. Now we'll extract the logic part with in-built hook - useState. and yes, we can use in-built hooks in our custom hook.
import { useState } from "react";

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

  const increment = () => {
    setCount((count) => count + 1);
  };

  const decrement = () => {
    setCount((count) => count - 1);
  };

  const reset = () => {
    setCount(0);
  };

  return [count, increment, decrement, reset];
};

export default useCounter;
Enter fullscreen mode Exit fullscreen mode

At last, we return all the necessary variables & functions - count, increment, decrement, reset in an array.

That's it, we just made our own custom hook. πŸŽ‰

Now we can use useCounter hook in our functional components.

We just need to import this hook & use it using array destructuring.

const [count, increment, decrement, reset] = useCounter();
Enter fullscreen mode Exit fullscreen mode

CounterOne.js

import React from "react";
import useCounter from "./useCounter";

const CounterOne = () => {
  const [count, increment, decrement, reset] = useCounter();

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

export default CounterOne;
Enter fullscreen mode Exit fullscreen mode

CounterTwo.js

import React from "react";
import useCounter from "./useCounter";

const CounterTwo = () => {
  const [count, increment, decrement, reset] = useCounter();
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
};

export default CounterTwo;
Enter fullscreen mode Exit fullscreen mode

Here's the code sandbox link - useCounter

Conclusion

Hope after reading this blog, now you know -

  1. how to create a custom hook.
  2. how to use it in a functional component.

If you find this blog as helpful, don't forget to share it.

Thank you πŸ™‚
Connect with me on - Twitter Instagram

Discussion (1)

Collapse
lukeshiru profile image
Luke Shiru • Edited on

This is pretty similar to a recent post of mine, but I think your approach here has 2 significant issues:

  1. Returning a quadruple (a tuple of length 4) can become pretty confusing because the devs need to know the order of all those values, and if you only need, let's say the third one, then is not great DX either: const [,,whatIWant] = useHook().
  2. Moving the state out of the Counter component just to use it inside the same component is not that useful, mainly because is just adding steps to achieve the same. Ideally you should make your component stateless and then use the hook from the parent component. Using one of the diagrams in my post, you're basically still doing this:

Diagram showing 3 components, each with its state inside, all wrapped by the app

I'm fully on board with you with the fact that devs need to use more custom hooks because that's where hooks actually shine, but I believe the particular approach you took could use a little more work.

Cheers!