Raphael Ugwu Writer, Software Engineer and a lifelong student.

Popular React Hook libraries

8 min read 2407

The journey of the React ecosystem has really been an interesting one. Since the advent of features like time slicing and suspense in React 16.3, we’ve had a series of interesting concepts from the awesome React team but none have been as eye-catching as React Hooks which got its first stable release in React 16.8.

Offering a cleaner way to write code while not having to worry about backward compatibility issues means it’s probably safe to say that Hooks are here to stay. In this blog post, I will depict how Hooks are lifesavers. I will illustrate a couple of use cases that will feature popular React Hook libraries – both mainstream and custom (created by enthusiasts like you and me). Let’s get started.

What are React Hooks?

Basically, Hooks provide a medium for passing state and properties without having to create class components. Adopting a function-based approach, with Hooks we can separate our logic from our UI such that it can be reused in other parts of our application as well. Take a look at both code samples below:

import React, { Component } from "react";
class MovieButton extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click to purchase movie tickets" };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Enjoy your movie!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return <button onClick={this.handleClick}>{buttonText}</button>;
  }
}
export default MovieButton

The gist above shows how the internal state of MovieButton is changed by setState when the button is clicked. Using Hooks, this internal state change can be depicted without having to depend on classes, constructors or setState:

import React, { useState } from "react";
export default function MovieButton() {
  const [buttonText, setButtonText] = useState("Click to purchase movie tickets");
  function handleClick() {
    return setButtonText("Enjoy your movie!");
  }
  return <button onClick={handleClick}>{buttonText}</button>;
}

I chose to show useState first because it’s the first hook introduced to the React ecosystem. useState is used to manage a component’s local state and preserve it between re-renders. What’s fascinating is that the component doesn’t have to be an ES6 class component – a basic JavaScript function is fine and we accomplish the same thing while reducing our codebase by ten lines. Implement useState by including a pair of variables – one to represent the actual starting state of your component and the other representing what you want your component’s state to be updated to.

Mainstream React Hook libraries

State and data fetching

Let’s say I wanted to create an application using just Hooks. Most likely, I would have to fetch data at some point. A good approach would be to begin with defining state wherever it needs to be defined. I’ll start by creating a component and fetching data from an API to be rendered by this component:

import React, { useState, useEffect } from "react";

const URL = "https://api.punkapi.com/v2/beers";
export default function Landing() {
  const [beer, setBeer] = useState([]);
  useEffect(() => {
    fetch(URL)
      .then(response => response.json())
      .then(beer => setBeer(beer));
  });
}

This brings us to the useEffect Hook. The useEffect Hook lets you handle lifecycle events directly inside function components. Activities such as setting up a subscription and data fetching which we would use lifecycle methods such as componentDidMount() to accomplish are now handled via useEffect. According to React’s documentation:

useEffect serves the same purpose as componentDidMount, componentDidUpdate, and componentWillUnmount in React class lifecycle methods, but unified into a single API

So in the above example instead of having a class component, I created a function and called the fetch method inside useEffect. There’s also no need to use this.setState to update state here as I created setBeer, a random function extracted from the useState Hook.

If you’ve been following up to this point and you try to run the application with the code sample above, you should encounter a very ugly infinite loop:

Why? useEffect serves the same purpose as componentDidMount, componentDidUpdate and componentWillUnmount. Because setBeer() updates the state of beer after every data fetch, the component is updated and useEffect goes ahead to fetch data again.

To avoid this bug, we need to specify that we only want to fetch data when the component mounts by providing an empty array as a second argument to useEffect:

import React, { useState, useEffect } from "react";

const URL = "https://api.punkapi.com/v2/beers";
export default function Landing() {
  const [beer, setBeer] = useState([]);
  useEffect(() => {
    fetch(URL)
      .then(response => response.json())
      .then(beer => setBeer(beer));
  }, {});
}

Form handling

Through custom Hooks (and there are tons of them in the ecosystem right now), React lets you reuse and share little bits of logic. As a rule of thumb, when there’s a lot of logic in a component, it’s a sign that you should refactor it and distribute some of the logic to avoid having bloated components. Let’s rely on custom Hooks to create some sort of interactivity with our app – say like a form where users can submit their data. react-hook-form is a library built entirely with Hooks and provides form validation. We’ll include it in our application like we would install an npm package:

npm i react-hook-form

And then import the custom Hook we need – useForm:

import React from "react";
import useForm from "react-hook-form";

const active = {
  fontSize: "15px"
};
export default function Purchase() {
  const { register, handleSubmit, errors } = useForm();
  const onSubmit = data => {  // upload the data retreived from the form to a database, return value to a user, etc
    console.log(data);
  };

  return (
    <div>
      <form onSubmit={handleSubmit(onSubmit)}>
        <label>Full Name</label>
        <input name="fullname" ref={register} />
        <label>Beer Name</label>
        <input
          name="beerName"
          ref={register({ required: true, maxLength: 10 })}
        />

        <select style={active} name="Title" ref={register({ required: true })}>
          <option value="">Select...</option>
          <option value="six-pack">Six Pack</option>
          <option value="twelve-pack">Twelve Pack</option>
        </select>
        <label>
          <input type="checkbox" placeholder="+18" name="+18" ref={register} />I
          am 18 and above
        </label>
        {errors.beerType && <p>This field is required</p>}
        <input type="submit" value="Pay Here" />
      </form>
    </div>
  );
}

An overview of how this works:

Routing

The application is gradually expanding, at this point, it would be great to include what every app with multiple components needs – routes. We’ll make use of hooksrouter – an awesome library that exports a custom hook useRoutes :

npm i hookrouter

useRoutes evaluates a predefined route object and returns a result when the routes match:

import React from "react";
import Purchase from "./components/Purchase";
import Landing from "./components/Landing";
import HomePage from "./components/HomePage";
const Routes = {
  "/": () => ,
  "/purchase": () => ,
  "/landing": () => 
};

export default Routes;

This trims down the excessive code we have to write when using traditional react Router as we would render the <Route/> component for all the individual routes in our app and pass props in them. Now, all we have to do is import the Routes component and pass it to the useRoutes Hook:

// index.js or where you choose to render your entire app from
import { useRoutes } from "hookrouter";
import Routes from "./router";

function App() {
  const routeResult = useRoutes(Routes);
  return <div>{routeResult}</div>;
}

Let’s see what navigating through the app feels like:

Handling complex state management

Of course useState is used to manage state but what if your app grows in complexity and you have to deal with multiple state transitions in one state object? This is exactly what the useReducer Hook is useful for. useReducer is preferred when you have to handle data in multiple objects or arrays and also keep this data maintainable and predictable. To depict the useReducer Hook, I’ll add a page with some multiple state architecture to the app – maybe a place where our users can create their own beer recipes:

import React, { useReducer } from "react";

const myStyle = {
  color: "white",
  fontSize: "20px"
};

export default function Recipe() {
  const initialState = {
    RecipePrice: 0,
    recipe: {
      price: 100,
      name: "Oompa Loompa",
      image:
        "https://res.cloudinary.com/fullstackmafia/image/upload/v1568016744/20110111-132155-Homebrew-Grain_uihhas.jpg",
      ingredients: []
    },
    stockpile: [
      { id: "1", name: "Extra Pale Malt", price: 10 },
      { id: "2", name: "Ahtanum Hops", price: 6 },
      { id: "3", name: "Wyeast 1056", price: 8 },
      { id: "4", name: "Chinook", price: 5 }
    ]
  };
  const reducer = (state, action) => {
    switch (action.type) {
      case "REMOVE_ITEM":
        return {
          ...state,
          RecipePrice: state.RecipePrice - action.item.price,
          recipe: {
            ...state.recipe,
            ingredients: state.recipe.ingredients.filter(
              y => y.id !== action.item.id
            )
          },
          stockpile: [...state.stockpile, action.item]
        };
      case "ADD_ITEM":
        return {
          ...state,
          RecipePrice: state.RecipePrice + action.item.price,
          recipe: {
            ...state.recipe,
            ingredients: [...state.recipe.ingredients, action.item]
          },
          stockpile: state.stockpile.filter(x => x.id !== action.item.id)
        };
      default:
        return state;
    }
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  const removeFeature = item => {
    dispatch({ type: "REMOVE_ITEM", item });
  };

  const addItem = item => {
    dispatch({ type: "ADD_ITEM", item });
  };

  return (
    <div className="boxes" style={myStyle}>
      <div className="box">
    <h4>Ingredients Stockpile</h4>
        <figure>
          <img width={"300px"} src={state.recipe.image} alt="my recipe" />
        </figure>
        <h2>{state.recipe.name}</h2>
        <pre>Amount: ${state.recipe.price}</pre>
        <div className="content">
          <h5>Added ingredients:</h5>
          {state.recipe.ingredients.length ? (
            <ol type="1">
              {state.recipe.ingredients.map(item => (
                <li key={item.id}>
                  <button
                    onClick={() => removeFeature(item)}
                    className="button"
                  >
                    REMOVE FROM LIST
                  </button>
                  {item.name}
                </li>
              ))}
            </ol>
          ) : (
            <pre>You can purchase items from the stockpile.</pre>
          )}
        </div>
      </div>
      <div className="box">
        <div className="content">
          {state.stockpile.length ? (
            <ol type="1">
              {state.stockpile.map(item => (
                <li key={item.id}>
                  <button onClick={() => addItem(item)} className="button">
                    ADD TO LIST
                  </button>
                  {item.name} (+{item.price})
                </li>
              ))}
            </ol>
          ) : (
            <pre>Nice looking recipe!</pre>
          )}
        </div>

        <div className="content">
          <h4>Total Amount: ${state.recipe.price + state.RecipePrice}</h4>
        </div>
      </div>
    </div>
  );
}

If you’re familiar with Redux, you’ll recognize line 54 in the code sample above where useReducer accepts a reducer with the initial state of the component and an action – usually, a dispatch method that is used to update the state of the component as desired. Thus with reducers, we can combine multiple states into one instead of having to create more than one single state Hook. Let’s see how this component works:

Hook collections

Since the release of Hooks, the enthusiasm from the React community has been amazing. Tons of custom Hooks have been created depicting awesome functionalities. Custom React Hook collections you should definitely check out include:

Collection of React Hooks which contains more than 300 custom hooks – popular among them is useArray – a Hook that provides multiple methods for array manipulation which is a developer’s everyday chore. Let’s update our app to include the useArray hook:

import React from "react";
import { useArray } from "react-hanger";

const myStyle = {
  color: "white"
};
export default function App() {
  const todos = useArray(["35cl", "50cl", "60cl"]);
  return (
    <div style={myStyle}>
      <h3>Measures</h3>
      <button
        onClick={() =>
          todos.add(Math.floor(Math.random() * (60 - 35 + 1)) + 35 + "cl")
        }
      >
        CUSTOM
      </button>

      <ul>
        {todos.value.map((todo, i) => (
          <div>
            <li key={i}>{todo}</li>
            <button onClick={() => todos.removeIndex(i)}>
              Remove from list
            </button>
          </div>
        ))}
      </ul>
      <button onClick={todos.clear}>clear</button>
    </div>
  );
}

Let’s see how that works:

Another collection I really find interesting is useHooks, which contains useLockBodyScroll , a Hook that prevents users from scrolling over a particular component. I observed that this Hook works with React’s inbuilt useLayoutEffect Hook – which reads layout from the DOM and re-renders synchronously. To implement useLockBodyScroll, you first need to define it as a function:

import { useLayoutEffect } from "react";

export default function useLockBodyScroll() {
  useLayoutEffect(() => {
    // Get original value of body overflow
    const originalStyle = window.getComputedStyle(document.body).overflow;
    // Prevent scrolling on mount
    document.body.style.overflow = "hidden";
    // Re-enable scrolling when component unmounts
    return () => (document.body.style.overflow = originalStyle);
  }, []); // Empty array ensures effect is only run on mount and unmount
}

Then import it in the desired component:

import useLockBodyScroll from "./useLockBodyScroll";

export default function Landing() {
    useLockBodyScroll();
    const [data, setData] = useState([]);
    useEffect(() => {
        fetch(URL)
            .then(response => response.json())
            .then(data => setData(data));
    }, []);
    return ( <
        div >
        <
        button >
        <
        A style = {
            {
                textDecoration: "none"
            }
        }
        href = "/" >
        HOME <
        /A>{" "} <
        br / >
        <
        /button> {
            data.map(item => ( <
                Item.Group key = {
                    item.id
                }
                style = {
                    divStyle
                } >
                <
                Item >
                <
                Item.Image width = "80"
                size = "tiny"
                src = {
                    item.image_url
                }
                alt = "Beer Flask" /
                >
                <
                Item.Content >
                <
                Item.Header > {
                    item.name
                } < /Item.Header> <
                Item.Extra > {
                    item.tagline
                } < /Item.Extra> <
                Item.Meta style = {
                    {
                        lineHeight: 1.5
                    }
                } > {
                    item.description
                } <
                /Item.Meta> <
                /Item.Content> <
                /Item> <
                /Item.Group>
            ))
        } <
        /div>
    );
}

Let’s see how that functions. The scrollbar in our browser should be absent:

There, our app is done for now. Did I forget something you feel is super important? You’re welcome to improve on the demo in CodeSandbox.

Summary

I think Hooks are the greatest thing to happen to React in a long time. Even though a lot has been achieved so far, there’s still so much we can do. Among React enthusiasts, there has been the debate in certain forums that React providing the facility to create custom Hooks would result in an overload of Hooks in the ecosystem – similar to what occurred with jQuery plugins. What’s your take on Hooks and what awesome Hooks have you discovered recently? Do let me know in the comments below. Cheers.

 

 

Plug: , a DVR for web apps

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Raphael Ugwu Writer, Software Engineer and a lifelong student.

3 Replies to “Popular React Hook libraries”

  1. Raphael, thanks for your article. I appreciate it!

    One thing that your code example doesn’t touch upon is where to where not to make useFetch calls. I went down a very wrong path by making such calls from event handlers like onClick, onChange, etc. If anyone reading this does the same, try a simple test with your code: Make a call to a given endpoint and then make the same call a second later. In many cases, the second call will not go out because the dependency(s) in the useEffect that makes the ajax call haven’t changed.

    Reading this article, and the comments therein, really helped me: https://blog.logrocket.com/frustrations-with-react-hooks/ Now, the only way I’ll make an ajax call is either: In a useEffect upon loading -or- by setting a local state or context property, which is a dependency of a useEffect and thus forces the code in that useEffect to be executed. The response data will then either populate a local state or context property, which in turn changes the appearance/behavior of a React component element.

    Changing my coding practices with React Hooks in this manner was a definite paradigm switch but one where things now work and there are no longer any “mysterious” bugs.

  2. Hi Robert,

    I’m glad you like my article. Thanks for the positive words.

    Your comment is very insightful, I haven’t tested for edge cases with the useEffect hook but this right here has prompted me to do so. Paul’s article which you recommended was also insightful as well. I will definitely be updating this post and its code demo with my findings.

  3. OMG I sooooo want to save others the wrong path I went down. My little litmus test of calling the same endpoint twice in succession is a super one to avoid the terrible bug I encountered.

    If I can get permission from my employer, I would love to publish the best practices code to use the Context API, useEffect, and calling API Endpoints that I’ve learned over the past few months.

Leave a Reply