useTransition hook allows you to mark certain updates as transitions so they can be deprioritized, allowing other, more urgent updates to be processed first. This ensures that the UI remains responsive during updates that might take some time.

import { useTransition, useState } from 'react';
import { Posts } from './Posts';
import { Home } from './Home';
import { Contact } from './Contact';
 
export function App() {
  const [isPending, startTransition] = useTransition();
  const [page, setPage] = useState('home');
 
  function changePage(newPage: string) {
    startTransition(() => {
      setPage(newPage);
    });
  }
 
  return (
    <>
      <button onClick={() => changePage('home')}>Home</button>
      <button onClick={() => changePage('posts')}>Posts</button>
      <button onClick={() => changePage('contact')}>Contact</button>
      <hr />
      {isPending && <div>Loading...</div>}
      {page === 'home' && <Home />}
      {page === 'posts' && <Posts />}
      {page === 'contact' && <Contact />}
    </>
  );
}
export function Home() {
  return <div>Home</div>;
}
export function Contact() {
  return <div>Contact</div>;
}

Posts component is artificially delayed by 500ms to emulate extremely slow code.

export function Posts() {
  const items = [];
  for (let i = 0; i < 500; i++) {
    items.push(<SlowPost key={i} />);
  }
  return <ul>{items}</ul>;
}
 
function SlowPost() {
  const startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // Do nothing for 1 ms per item to emulate extremely slow code
  }
 
  return <li>Post</li>;
}

Now when you click on the Posts button, you’ll notice that the UI remains responsive and you can still switch to other pages while the posts are loading. Try removing the startTransition wrapper around setPage in changePage to see the difference.