DEV Community

Cover image for Popular React Hook libraries
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

Popular React Hook libraries

Written by Raphael Ugwu✏️

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.

LogRocket Free Trial Banner

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
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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));
  });
}
Enter fullscreen mode Exit fullscreen mode

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));
  }, {});
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>;
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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>
    );
}
Enter fullscreen mode Exit fullscreen mode

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.


Editor's note: Seeing something wrong with this post? You can find the correct version here.

Plug: LogRocket, a DVR for web apps

 
LogRocket Dashboard Free Trial Banner
 
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.
 
Try it for free.


The post Popular React Hook libraries appeared first on LogRocket Blog.

Top comments (3)

Collapse
 
serializator profile image
Julian • Edited

I think you used the wrong term in the part about useEffect. It says to pass an empty array, but you're passing an empty object. It might catch someone off guard and scratch their head (which is a good thing, but might not be in this case, hahah)

Collapse
 
holtmansfield profile image
Holt Mansfield

I think hooks are brilliant and a very natural evolution of react. Components are presentational. So wrapping with HOC's or using render props for sharing non-presentational things always felt wrong. Especially with render props. I've been using nothing but hooks since April and I don't ever want to go back to the old paradigm.

Collapse
 
afewminutesofcode profile image
Aaron

Thanks for sharing this information! I will definitely look at react-hook-form on my next project!