Mar 21, 2022 5 min read React Hooks, DIY and mental model

Back in 2018 the React Hooks proposal changed the way we all write React code. The community as a whole has given it an extremely positive response and we can now write full application without relying on Class Components, we can further harness the power of hooks in our applications to reuse code effectively and distribute stateful functionality in our application. Let’s form a mental model to develop our own hooks for our stateful logic! I have hosted a webinar on the same topic, watch it if you are more of a ‘video’ person

Prerequisites

Here are some concepts you should be knowing before you make full use of this blog post

  • Basic knowledge of React and writing Functional Components
  • Basic Hooks such as useState and useEffect
  • JS basics

Getting Started

for a great interactive way to get started is by cloning the react-hooks session repo

git clone https://github.com/antstackio/react-hooks-webinar.git

alternatively you can find the code sandbox here

The codebase looks like this:

src
┣ scaffold
┃ ┣...
┣ 1.jsx
┣ 1_class.jsx
┣ 2.jsx
┣ 3.jsx
┣ 4.jsx
┣ index.css
┣ main.jsx
┣ useDebounce.js
┗ useInput.js

Lets revise a hook

take a look at 1.jsx it says lets make a counter, which we can make using a simple useState hook

import React, { useState } from "react";

const StageOne = () => {
  const [count, setCount] = useState(0);
  const incrementCount = () => {
    setCount((prevCount) => prevCount + 1);
  };
  return (
    <div>
        <div>Count {count}</div>
        <button onClick={incrementCount}>Click Me!</button>
      </div>
    </div>
  );
};

export default StageOne;

This is how it looks, neat right! but you already know this

Lets make our first custom hook!

When we say custom hooks we do not actually program a new react feature, but actually compose a new functionality by putting meaningful pre-existing hooks!

Time to Toggle!

We often need a hook for toggling a boolean value but it comes with a boilerplate code and often is messy when dealing with multiple toggles. Let’s create a useToggle hook!

// useToggle.jsx
const useToggle = initialValue => {
  const [state, setState] = useState(initialValue && true)
  const toggle = () => {
    setState(prevState => !prevState)
  }
  return [state, toggle]
}

We can consume it as

const [booleanState, toggleFunction] = useToggle(true) // optional param

// toggle booleanState by simply calling the toggleFunction
toggleFunction()

Multiple Toggles are made easier

const [booleanState1, toggleFunction1] = useToggle(true)
const [booleanState2, toggleFunction2] = useToggle(false)
const [booleanState3, toggleFunction3] = useToggle(true)
// All independent states

Abstracting Repeated Logic to hooks

Implementing logic for something line a text input has some logic that can be abstracted into a custom hook so that we can have common functionality isolated and reduce the scaffolding

// useInput.tsx
const useInput = () => {
  const [value, setValue] = useState("")
  const handleChange = e => {
    setValue(e.target.value)
  }
  return {
    value,
    handleChange,
  }
}
// All inputs in your app can use this, future changes/tweaks can reflect everywhere.

Saving $ with hooks

While this was a rather simple useCase, there are a lot of ways to use the power of hooks and make complex logic re-usable. Let’s take something like De-bouncing for example.

Debouncing is actually a technique derived from electronics where sending voltages continously to a component might damage it so it involves sending a signal only when there is a ‘calm’ after certain actions.

In software we use it to save money!, usually a website has a search box with suggestions, each call invokes a api call that runs an expensive function behind the scened causing money lost in bandwidth and cpu time. The best way to optimize this would be to wait for 100 or 200ms after the user has input their initial key-word to fire that call. Let’s implement this via a custom hook!

Time to DeBounce

import { useCallback } from "react"
export const useDebounce = (delay, callback) => {
  const debounce = function (d, fn) {
    let timerId
    return function (...args) {
      if (timerId) {
        clearTimeout(timerId)
      }
      timerId = setTimeout(() => {
        fn(...args)
        timerId = null
      }, d)
    }
  }

  const debouncedCallback = useCallback(debounce(delay, callback), [])
  return debouncedCallback
}

Above code implements debouncing in a simple way.

We use setTimeout to set a timeout equal to delay provided. if function is called more than once the timerId is re-set until there is moment of calm and the setTimeout executes properly. We memoize this function to avoid re-rendering it if its parent re-renders.

we consume it as

const callback = () => {
  console.log("I have been Called")
}
const debouncedAPICall = useDebounce(500, callback)
//wont call debouncedAPICall until there is 500ms of calm

Add this to something like an input

<input onChange={debouncedAPICall}></input>
// Will print only after 500ms of user typing on input

And there you have it, custom hooks, the why, what and how. We saw a couple of pre-existing hooks, combined them to serve our own custom use-case and made something as complex as debouncing pretty easy.

Ending Notes

Hopefully this blog clears the fear of custom hooks out of you and helps you write better code. I have implemented the practice of separating all logic into a hook and keeping presentation layer separate in my projects and it has given great results with extremely clean codebase that can be easily maintained and minimal repeated code. I hope you also implement this is your codebase to get a boost in your overall Developer Experience.The possibilities opened by custom hooks are truly limitless.

Author(s)

Topics

Share this blog

React Hooks, DIY and mental model

5 min read

Mar 21, 2022

Mar 21, 2022

5 min read

Mar 21, 2022

Frontend bundlers minified

8 min read

Feb 25, 2022

Feb 25, 2022

8 min read

Feb 25, 2022

This website stores cookies on your computer.

These cookies are used to collect information about how you interact with this website and allow us to remember you. We use this information in order to improve and customize your browsing experience and for analytics and metrics about our visitors on this website.

If you decline, your information won’t be tracked when you visit this website. A single cookie will be used in your browser to remember your preference not to be tracked.