Write Your Own Custom React Hooks from the Scratch

Share with your friends!

React introduces lots of new features and fixes from version 16. These changes took React several steps up in the front end frameworks. Hooks are a newly introduced feature in React 16.8.0. They let you use state and other React features without writing a class and many other cool features that make the development easy.

React also allows writing custom React Hooks in order to encourage writing well manageable and readable components. If you are new to Hooks, React’s official docs is a great place to start.

The goal of this article is going to be on building custom React hooks and best practices.

React provides a bunch of hooks that can be used right away such as useState, useEffet, useMemo. Redux, React-router introduced their own set of hooks for handling their existing code flows. There are several third-party npm packages such as useLocalStorage are also available.

a usage of useState hook
a usage of useState hook

Why Custom Hooks?

Hooks were introduced to have stateful stuff in functional components as mentioned. The need of hooks arises when you are trying to convert an existing class component into a functional component or writing a functional component from scratch. There can be a situation like you need to write the same code segments in different components or you want to have some separation between component and component logic. That’s where writing a custom React Hooks is coming into play.

There are three main things to remember when writing custom React Hooks.

1. Better to add “use” prefix before the name of the hooks
2. Hooks are just functions either return something or nothing returns
3. Only Call Hooks from React Functions

Coding time!

It's time to get your hands dirty
taken from the internet

These are the basics you need to know about Hooks. Let’s Check the simple application below. This application validates a given email address.

const TextInput = () => {
  const [letterCount] = useState(20);
  const [currentLetterCount, setCurrentLetterCount] = useState(letterCount);

  const handleChange = e => {
    const text = e.target.value;
    if (e.target.value) {
      setCurrentLetterCount(letterCount - text.length);
    } else {
      setCurrentLetterCount(letterCount);
    }
  };

  return (
    <div className="container">
      <div>
        <input type="text" onChange={handleChange} />
      </div>
      <span>{ currentLetterCount >= 0 ? 
        `You have ${currentLetterCount} characters left` : 
        `You have exceeded by ${Math.abs(currentLetterCount)} characters`}
      </span>
    </div>
  );
};

If we consider TextInput as an isolated component, the letter count behaviour is not a part of TextInput but it helps to define the behaviour of the TextInput Component. Also, If we need to implement this for another input type we will have to define the whole behaviour for that input element as well.

Let’s go through the application code.

This application indicates the count of characters left to write in a text message. This contains one input element to input value and one span element to display letter count text.

  1. <input /> element contains onChange event which triggers every time user types and handleChange is the given function to execute.
  2. <span /> element text contains a ternary operator to decide the display text.

Let’s think about this implementation in Hooks perspective. As I mentioned Hooks allows to separate UI and logic. The logic in this implementation is the decision of letter count and the result text.

Whenever you see a component knowing more than it should, it should a sign that you might need to refactor it.

function useLetterCount(count) {
  const [letterCount] = useState(count);
  const [text, setText] = useState(`You have ${count} characters left`);

  const onChange = (e) => {
    const value = e.target.value;
    const currentLetterCount = letterCount - value.length;

    if (value.length > 0) {
      setText(`You have ${currentLetterCount} characters left`);
    } else {
      setText(`You have exceeded by ${Math.abs(currentLetterCount)} characters`);
    }
  }

  return [text, onChange];
}

I extracted the letter count logic to a new function which returns a triggering function and text. In this way, we will be able to separate the additional states live in the TextInput component. I gave a name as useLetterCount which reflects that this is a hook and its purpose.

what do you think?
taken from the internet

What I have done?

  1. Write a proper name that explains the behaviour of the hook with use prefix.
  2. Extract state values first.
  3. Decide the shape of the return data object. You can return many than a variable. It’s up to you to decide the shape of the return object. The recommended count is two.
  4. Copy the logic resides in the component and move it to the hook. Make sure the do the needed refactoring while keeping the logic the same as before.
  5. Since hooks suppose to be simple, easy to understand and easy to maintain make sure to separate the tasks using functions within the hook.
Confused

Don’t worry if you cannot understand the conversion to hooks. You need to think in a way of abstraction with your codebase. Check whether you have repeated code segments or if you can find any possible separation between component and logic.

The final code

function useLetterCount(count) {
  const [letterCount] = useState(count);
  const [text, setText] = useState(`You have ${count} characters left`);

  const onChange = (e) => {
    const value = e.target.value;
    const currentLetterCount = letterCount - value.length;

    if (value.length > 0) {
      setText(`You have ${currentLetterCount} characters left`);
    } else {
      setText(`You have exceeded by ${Math.abs(currentLetterCount)} characters`);
    }
  }

  return [text, onChange];
}

const TextInput = () => {
  const [text, onChange] = useLetterCount(20);

  return (
    <div className="container">
      <div>
        <input type="text" onChange={onChange} />
      </div>
      <span>{text}</span>
    </div>
  );
};

The code structure is changed a lot with introducing hooks. If you check for the rendering element it is changed like this.

Before

<div className="container">
  <div>
    <input type="text" onChange={handleChange} />
  </div>
  <span>{ currentLetterCount >= 0 ? 
    `You have ${currentLetterCount} characters left` : 
    `You have exceeded by ${Math.abs(currentLetterCount)} characters`}
  </span>
</div>

After

<div className="container">
  <div>
    <input type="text" onChange={onChange} />
  </div>
  <span>{text}</span>
</div>

The size of the code and the complexity of the code is reduced. Use of Hooks allowed to reduce the complexity and separate the component and the logic in a better way. You can use as much as basic hooks or custom hooks inside a custom hook. It’s much easy and cleaner than render props and HOC.

Final thoughts

Custom Hooks provides the flexibility of sharing logic that wasn’t possible in React components before. You can write custom Hooks that large area of use cases like form handling, animation, declarative subscriptions, timers, and probably many more I haven’t mentioned.

Try to apply abstraction too early. Now that function components can do more, it’s likely that the average function component in your codebase will become longer. This doesn’t mean that you have to immediately split it into Hooks since this is normal. But you need to start identifying scenarios where a custom Hook could hide complex logic behind a simple interface, or redesign a messy component.

Thanks for reading. Did this article help you out? If it did, I hope you consider sharing it. You might help someone else out. Thanks so much!

Share with your friends!

Leave A Comment

shares