Recently, React announced a feature of the React ecosystem — Concurrent Mode. This would allow us to stop or delay the execution of components for the time that we need. It’ll help React apps stay responsive and gracefully adjust to the user’s device capabilities and network speed.
Concurrent Mode consists of a set of new features — one of the biggest ones is Suspense and a new approach to data fetching.
Basically, there are three ways to do it:
fetch
in useEffect
.Container
component that handles data fetching and conditionally renders the child presentational component once we’ve received everything.I believe that the concepts of the first two approaches are well known and definitely presented in your code. Let’s dive straight into the render-as-you-fetch approach.
You’ve probably noticed that the explanation of this approach has two parts:
Let’s build an app together that loads major stock indexes. For that, we have a simple “Load” button. Once you click on it, we start loading data immediately:
const App = () => {
const [prefetchedIndexes, setPrefetchedIndexes] = useState();
return (
<>
<button
onClick={() => {
setPrefetchedIndexes(prefetchQuery(`${API}/majors-indexes`));
}}
>
Load all indexes
</button>
{prefetchedIndexes && (
<IndexList prefetchedIndexes={prefetchedIndexes} />
)}
</>
);
};
prefetchQuery
is a function that performs the fetch
request and returns an object that we’re going to pass to the <IndexList />
component. The key takeaway from this example is that we’re triggering fetch from the onClick
event and not in the render phase.The second part of the example above is that we’re saving the object from
prefetchQuery
to the state and starting to render <IndexList />
immediately as well.On the other hand, we also don’t want to render the list with empty data, so ideally, we’d like to be able to suspend render until we have all the data without writing
if (isLoading) return null
. Luckily, we have the Suspense component for exactly that purpose.
Suspense is a mechanism for data-fetching libraries to communicate to React that the data a component is reading is not ready yet.
React can then wait for it to be ready and update the UI.
Let me show you an example:
const IndexList = ({ prefetchedIndexes }) => {
const data = usePrefetchedQuery(prefetchedIndexes);
return data.majorIndexesList.map(index => (
<div key={index.ticker}>
Show {index.ticker}
</div>
));
};
const App = () => {
const [prefetchedIndexes, setPrefetchedIndexes] = useState();
return (
<>
<button
onClick={() => {
setPrefetchedIndexes(prefetchQuery(`${API}/majors-indexes`));
}}
>
Load all indexes
</button>
{prefetchedIndexes && (
<Suspense fallback={<span>Loading indexes list...</span>}>
<IndexList prefetchedIndexes={prefetchedIndexes} />
</Suspense>
)}
</>
);
};
To take advantage of Suspense, you just need to wrap your component with it. It accepts a
fallback
prop: the element that you want to show while waiting for data.Now that you know about Suspense and prefetch practices, you wonder how this all works together. So, here is the last piece of this puzzle. To solve it, let’s finally check out the
prefetchQuery
function.function wrapPromise(promise) {
let status = "pending";
let result;
let suspender = promise.then(
r => {
status = "success";
result = r;
},
e => {
status = "error";
result = e;
}
);
return {
read() {
if (status === "pending") {
throw suspender;
} else if (status === "error") {
throw result;
} else if (status === "success") {
return result;
}
}
};
}
// Function that reads resource object
// It could also include Cache logic
export const usePrefetchedQuery = prefetchedQuery => prefetchedQuery.read();
export const prefetchQuery = (input, init) => {
// Make fetch request
const promise = fetch(input, init).then(response => response.json());
// Return resource for Suspense
return wrapPromise(promise);
};
Don’t be scared by the complexity of it, it’s actually fairly simple.
First, we take a URL and pass it to the native
fetch
function, receive a promise, and pass it to the wrapPromise
function. This function returns an object with the
read()
method:In fact, the only difference we have, compared to traditional fetching practices, is throwing a pending promise.
When you have
usePrefetchedQuery
in IndexList
, it just executes the read()
method. If data is not there yet, it throws a promise before actually rendering anything and Suspense
will catch that.The React team introduced an experimental
releases branch with a modern API.For that, you need to run
npm i react@experimental react-dom@experimental
and play with it locally. I also created a live example on for you that shows everything I did together in one working project.No. Concurrent mode is still under development and some implementation details could change. Use experimental versions to get familiar with new concepts and maybe propose your own ideas.
For example, how to integrate prefetch practices in routers or provide a good way to cache data.