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.
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.
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:
Screen Recording 2019 09 06 at 10 06 33 AM
Uploaded by Raphael Ugwu on 2019-09-06.
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)); }, {}); }
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:
Screen Recording 2019 09 06 at 12 13 16 PM
No Description
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:
Screen Recording 2019 09 06 at 1 50 31 PM
Uploaded by Raphael Ugwu on 2019-09-06.
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:
Screen Recording 2019 09 09 at 9 50 34 AM
Uploaded by Raphael Ugwu on 2019-09-09.
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:
Screen Recording 2019 09 06 at 4 29 24 PM
Uploaded by Raphael Ugwu on 2019-09-06.
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:
No Title
Uploaded by None on 2019-09-06.
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowUnderstanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
Matcha, a famous green tea, is known for its stress-reducing benefits. I wouldn’t claim that this tea necessarily inspired the […]
Backdrop and background have similar meanings, as they both refer to the area behind something. The main difference is that […]
3 Replies to "Popular React Hook libraries"
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.
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.
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.