How to Enhance React Apps with useTransition Hook

cover
24 Jun 2024

React is a popular JavaScript library for building user interfaces. It’s known for its efficiency and focus on creating reusable UI components. One of the key features in React is the introduction of hooks which are function that hooks into the React state. One of those hooks is the useTransition hook. This hook allows the state change to happen without blocking the interface which results in a smooth experience.

Understanding useTransition hook

Let’s look at the following example to understand the useTransition hook better:

import {useState} from "react"

const App = () => {
  const [post, setPost] = useState(undefined)

  const fetchData = async () => {
    await fetch("https://jsonplaceholder.org/posts/1")
      .then((result) => result.json())
      .then((result) => setPost(result))
  }

  return(
    <div>
      {post !== undefined && (
      <div className="post-card">
        <h2>{post?.title}</h2>
        <image src={post?.image} />
        <p>{post?.content}</p>
      </div>
      )}
      <button onClick={fetchData}>
        Get post
      </button>
    </div>
  )
}

export default App; 

When the user clicks the button, the UI may freeze during the fetch task depending on the internet speed or the complexity of the task executed inside the fetchData function. This can result in a poor user experience. Additionally, there's a risk of users spamming the button, potentially causing abuse of your application. These issues can easily be fixed using the useTransition hook.

We can update the previous code to something like this:

import {useState, useTransition} from "react"
import {ImSpinner2} from "react-icons/im"

const App = () => {
  const [pending, startTransition] = useTransition()
  const [post, setPost] = useState({})

  const fetchData = () => {
    startTransition( async () => {
      await fetch("https://jsonplaceholder.org/posts/1")
        .then((result) => result.json())
        .then((result) => setPost(result))
    })
  }

  return(
    <div>
      {post !== undefined && (
      <div className="post-card">
        <h2>{post.title}</h2>
        <image src={post.image} />
        <p>{post.content}</p>
      </div>
      )}
      <button
        disabled={pending} 
        onClick={fetchData}>
        {pending ? <ImSpinner2 className="animate-spin" /> : "Get post" }
      </button>
    </div>
  )
}

export default App; 

The invoked useTransition hook returns two values: pending, which is true if the task is being executed, and a startTransition function, which contains the task that may be interrupted by more urgent tasks.

In the example above, we wrap the fetch request inside an asynchronous arrow function within the startTransition function.

We then modify the button to include a disabled attribute linked to pending, and change the label to show a spinner when the task is pending and "Get Post" when the task is not pending.

This results in a smooth user experience, better performance, and protects your application from user misbehavior.

Conclusion

The useTransition hook is a game-changer for building high-performing React applications with smooth user experience. It ensures that the UI remains responsive during potentially slow operations and prevents UI freeze which enhances the overall user experience.