Exploring New “use” Hook in React

Bantawa Pawan
readytowork, Inc.
Published in
5 min readApr 8, 2023

The new use hook in React is incredible! Not only is it completely different than every other React hook because it ignores every single rule that the other hooks have to follow, but it also makes all the boilerplate code that you write when dealing with asynchronous code something of the past, which is something we all love because asynchronous code is everywhere.

In this article, we’ll explore how you can use the use hook to take an entire component and reduce it to about two lines of code. That’s how powerful this Hook is!

First, it’s essential to understand that this Hook is rather new, and it’s only under the experimental version of React. So, you need to make sure you’re using the experimental version of React and React DOM to start trying out and using this hook.

  "dependencies": {
"react": "experimental",
"react-dom": "experimental"
},

And because it is experimental, it means things could change. If things do change, you can always learn more about it from the documentation.

App.tsx

import { useState } from "react"
import { Data } from "./Data"
const URLS = {
USERS:
"https://dummyjson.com/users?limit=3&select=id,firstName,lastName,gender,age",
POSTS: "https://dummyjson.com/posts?limit=3&select=id,title,body",
COMMENTS: "https://dummyjson.com/comments?limit=3",
}
function App() {
const [url, setUrl] = useState(URLS.USERS)
const handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setUrl(event.target.value)
}
return (
<div className="App">
<div className="radios">
<label>
<input
type="radio"
value={URLS.USERS}
checked={url === URLS.USERS}
onChange={handleUrlChange}
/>
Users
</label>
<label>
<input
type="radio"
value={URLS.POSTS}
checked={url === URLS.POSTS}
onChange={handleUrlChange}
/>
Posts
</label>
<label>
<input
type="radio"
value={URLS.COMMENTS}
checked={url === URLS.COMMENTS}
onChange={handleUrlChange}
/>
Comments
</label>
</div>
<Data url={url} />
</div>
)
}

export default App

Now, let’s talk about what this base project is. We’re looking at a simple app component that has a URL and three radio buttons at the top that allows you to change that URL.

Data.tsx

import { useEffect, useState } from "react"
interface IProps {
url: string
}
export const Data: React.FC<IProps> = ({ url }) => {
const [isLoading, setIsLoading] = useState(true)
const [isError, setIsError] = useState(false)
const [data, setData] = useState()

useEffect(() => {
setIsError(false)
setIsLoading(true)
setData(undefined)

fetch(url)
.then((res) => res.json())
.then(setData)
.catch(() => setIsError(true))
.finally(() => setIsLoading(false))
}, [url])

return isLoading ? (
<h1>Loading....</h1>
) : isError ? (
<h1>Error</h1>
) : (
<pre>{JSON.stringify(data, null, 2)}</pre>
)
}

As you can see, you briefly get a loading State before it moves you on and actually renders out the data. The way all that has happened is inside the data component, which has a simple fetch request.

When a fetch request returns a promise, we can either have our data or we can have an error. We set our state to be loading, error, and data, then inside of our fetch() function, we make sure to get the data and set an error if we get it; if everything works out OK, we change our loading state from false to true so that it shows a loading icon if there is an error.

This is a bit cumbersome, and dealing with anything asynchronous such as promises and React has always been a significant pain, but the use hook solves all of those problems for us. We can get rid of all of our useState, our useEffect, and the rest of our code, so all we’re doing is a simple fetch request.

Data.tsx (After change)

import { use } from "react"
interface IProps {
url: string
}
export const Data: React.FC<IProps> = ({ url }) => {
const data = use(fetch(url).then((res) => res.json()))

return <pre>{JSON.stringify(data, null, 2)}</pre>
}

To use the use hook, we pass a promise to it. This fetch promise returns either JSON data or an error that we set to a variable called Data. The way that this hook works is very similar to if you had done something like await this fetch, where it’s going to give you the data or it’s going to throw an error that you have to catch somewhere else.

App.tsx (After Change)

import { Suspense, useState } from "react"
import { Data } from "./Data"
import ErrorBoundry from "./ErrorBoundary"
const URLS = {
USERS:
"https://dummyjson.com/users?limit=3&select=id,firstName,lastName,gender,age",
POSTS: "https://dummyjson.com/posts?limit=3&select=id,title,body",
COMMENTS: "https://dummyjson.com/comments?limit=3",
}
function App() {
const [url, setUrl] = useState(URLS.USERS)
const handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setUrl(event.target.value)
}
return (
<div className="App">
<div className="radios">
<label>
<input
type="radio"
value={URLS.USERS}
checked={url === URLS.USERS}
onChange={handleUrlChange}
/>
Users
</label>
<label>
<input
type="radio"
value={URLS.POSTS}
checked={url === URLS.POSTS}
onChange={handleUrlChange}
/>
Posts
</label>
<label>
<input
type="radio"
value={URLS.COMMENTS}
checked={url === URLS.COMMENTS}
onChange={handleUrlChange}
/>
Comments
</label>
</div>
<ErrorBoundry fallback={<div>{"Error..."}</div>}>
<Suspense fallback={<div>{"Loading..."}</div>}>
<Data url={url} />
</Suspense>
</ErrorBoundry>
</div>
)
}

export default App

We need to handle the loading and the error State, and we do that inside our app component using something called React suspense. We wrap our child component in the suspense component, which takes a fallback, and this fallback is going to be rendered anytime we’re in a loading state. Otherwise, once we’re done loading everything, it’s going to render out our data.

To catch errors in our code, we need to use something called an error boundary. This is especially common when working with the suspense.

To start, let’s create some basic state to determine if there is an error, and if so, what that error is. Then, we can get our Drive state from that error.

Next, we’ll use a prop called “fallback” to render an error message if there is an error. If there is no error, then we’ll simply render whatever is inside the error boundary. This approach works very similarly to how we handle the suspense.

ErrorBoundry.tsx

import React from "react"
class ErrorBoundry extends React.Component {
state = { hasError: false, error: null }

static getDeliveredStateFromError(error: any) {
return {
hasError: true,
error,
}
}
render() {
if (this.state.hasError) {
return this.props.fallback
}
return this.props.children
}
}

export default ErrorBoundry

By implementing error boundaries, we can ensure that our code runs smoothly and catches any errors that may occur. So, next time you’re working with suspense or just want to be more proactive in catching errors, give an error boundary a try!

If you want to try out this use hook and see how it can transform your code, make sure you’re using the experimental version of React and React DOM. With this Hook, you can reduce an entire component to two simple lines, and that’s all it takes to do asynchronous code!

--

--