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.
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.
Editor's note: Seeing something wrong with this post? You can find the correct version here.
Plug: LogRocket, 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.
Try it for free.
The post Popular React Hook libraries appeared first on LogRocket Blog.
Top comments (3)
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)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.
Thanks for sharing this information! I will definitely look at react-hook-form on my next project!