19
loading...
This website collects cookies to deliver better user experience
Content
component for said tab fetches some data. While that data is being fetched, we render a little loading component in the content's place. This isn't the worst experience and indeed it's more-or-less the standard way we see loading states implemented in apps today.useTransition
hook.ReactDOM.render
, we use the new ReactDOM.createRoot
:import ReactDOM from "react-dom";
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
Suspense
:<Suspense fallback={<Loader />}>
{tab === 0 && <Content page="Uno" resource={resource} />}
{tab === 1 && <Content page="Dos" resource={resource} />}
{tab === 2 && <Content page="Tres" resource={resource} />}
</Suspense>
Suspense
when lazy loading components. However in this case our components aren't lazy loaded at all! Instead of suspending on the async loading of the component itself, we're now suspending on the async loading of data within it.Content
, we see a peculiarly simple component:function Content({ page, resource }) {
const time = resource.delay.read();
return (
<div className="tab-content">
This content is for page "{page}" after {time.toFixed()}
ms.
<p>{CONTENT[page]}</p>
</div>
);
}
time
, which would probably be set in state, for example maybe something like:const [time, setTime] = useState();
useEffect(() => {
resource.then((data) => {
setTime(data)
})
}, [])
return time &&
(<div className="tab-content">
This content is for page "{page}" after {time.toFixed()}
ms.
<p>{CONTENT[page]}</p>
</div>
);
jsx
being unconditionally returned. Further time
isn't set in state to trigger a rerender, rather its set to resource.delay.read()
. And that's the clue to how this is all working!fakeAPI
file, that resource.delay
is actually a special kind of promise, which in our naive implementation taken from the official React examples, is essentially a simplified mock of what something a React 18 compatible data fetching library would provide (and what Relay already does provide!).Suspense
wrapped components will be able to continuously check if the async data a component is attempting to read has been resolved, throwing and continuing to render the fallback until it's ready.Suspense
, implementing components that depend on async data is much more straight-forward. By itself though, we still can't easily control our loading states. We need the other major piece of our puzzle: the new and shiny useTransition
hook.useTransition
to tell React that setting the new tab and setting the new resource (which in turn fetches the tab's data) are both transitional state changes and as such we want it to hold off on rendering the resulting UI.startTransition
, which we get from useTransition
:const [isPending, startTransition] = useTransition();
function handleClick(index) {
startTransition(() => {
setTab(index);
setResource(fetchData());
});
}
startTransition
we get another utility: isPending
. As you can probably guess, this returns true
while our transitional changes are still ongoing. This can be used to show an extra piece of loading state so the user knows something is happening in the background. <GlobalLoader isLoading={isPending} />
// ...
<div className={`tab ${isPending ? "pending" : null}`}>
// ...
startTransition
and handling other UX details with isPending
🙌