Today our Top-senior referent Pia Rovira is shedding light on React18 and Suspense component.
It’s a React component (available from React18) that lets you display a fallback declaratively until its children have finished loading.
It’s not a data fetching library, nor is it a way to manage state, it just lets your components communicate to React that they are waiting for some data.
Suspense does not detect when data is fetched inside an Effect or event handler.
⚠️ Only Suspense-enabled data sources will activate the Suspense component. Suspense is kind of new, so not every data-fetching library is integrated to it yet. Suspense enabled data sources include:
lazy
use
Suspense component receives 2 props.
With Suspense we can go from:
const App = () => {
const [userDetails, setUserDetails] = useState({})
useEffect(() => {
fetchUserDetails().then(setUserDetails)
}, [])
if (!userDetails.id) return <p>Fetching user details...</p>
return (
<div className="app">
<h2>Simple Todo</h2>
<UserWelcome user={userDetails} />
<Todos />
</div>
)
}
to:
const data = fetchData() // this method should be integrated with Suspense
const App = () => (
<Suspense fallback={<p>Fetching user details...</p>}>
<UserWelcome />
</Suspense>
)
const UserWelcome = () => {
const userDetails = data.userDetails.read();
return <div>Hello {userDetails.name}!</div>
}
Code is cleaner and userDetails is always defined. While data is loading, we will see <p>Fetching user details...</p>
and then, when data promise resolves we will see the UserWelcome
component.
We can also nest Suspense
components, cause suspense does not suspend 🤯. For example:
<Suspense fallback={<BigSpinner />}>
<Biography />
<Suspense fallback={<AlbumsGlimmer />}>
<Panel>
<Albums />
</Panel>
</Suspense>
</Suspense>
In this case, At first we will see the BigSpinner
while Biography
data is being fetched. Then, we will see Biography
component and the AlbumsGlimmer
component while the albums data is loading.
Lastly, when having biography data and albums data, we will see Biography
and Albums
components.
So, just to see the evolution of our components:
1. Everything is loading
<BigSpinner />
2. Biography data already loaded, albums data is still fetching
<>
<Biography />
<AlbumsGlimmer />
</>
3. Biography data and albums data fetched
<>
<Biography />
<Panel>
<Albums />
</Panel>
</>
React-query is one of the libraries that has hooks that integrate with Suspense on the latest version v5
.
I’ll add an example below that includes Suspense and Error handling.
import React, { Suspense } from 'react';
import { QueryErrorResetBoundary } from '@tanstack/react-query';
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
import { Cards, Footer, Header, Main, Wrapper } from 'components';
import GlobalStyle from 'styles/globalStyles';
const Loading = () => <div>Loading...</div>
const ErrorView = ({ error, resetErrorBoundary }: FallbackProps) => {
return (
<div>
<div>{error.message}</div>
<button title='Retry' onClick={resetErrorBoundary} />
</div>
);
};
const Home: React.FC = () => {
return (
<Wrapper>
<>
<GlobalStyle />
<Header />
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary onReset={reset} FallbackComponent={ErrorView}>
<Suspense fallback={<Loading />}>
<Main />
</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
<Cards />
<Footer />
</>
</Wrapper>
);
};
export default Home;
And the Main component:
import React from 'react';
import { Button } from 'components';
import { useSuspenseQuery } from '@tanstack/react-query';
import { getData } from 'services/api';
export const Main: React.FC = () => {
const queryResponseRQ = useSuspenseQuery({
queryKey: ['deutschland'],
queryFn: () => getData('name/deutschland'),
});
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
paddingTop: 32,
paddingBottom: 32,
}}>
<div>{queryResponseRQ.data.data[0].name.common}</div>
</div>
);
};
Good news here is that errors and loading states are handled by Suspense
and ErrorBoundary
and so when Main
component displays the query Response data is always defined.
No need to using ?
or verifying to check nothing crashes.
<SuspenseList>
coordinates the “reveal order” of the closest <Suspense>
nodes below it.
<code>❗ Still not available in React 18. But will be included in a future release.</code>
"forwards"
, "backwards"
or "together"
. And it is the prop that indicates how the content will be revealedfunction ProfilePage({ resource }) {
return (
<SuspenseList revealOrder="forwards">
<ProfileDetails resource={resource} />
<Suspense fallback={<h2>Loading posts...</h2>}>
<ProfileTimeline resource={resource} />
</Suspense>
<Suspense fallback={<h2>Loading fun facts...</h2>}>
<ProfileTrivia resource={resource} />
</Suspense>
</SuspenseList>
);
}
Keep on exploring our blog post about React and React Native Improved Architecture. Some more tips are coming up…
Check out our other posts for more insights!
Leave a Reply