This article will explore how you should use the awesome useSWR hook for remote data fetching.

Why the SWR hooks for remote data fetching?
Because SWR (stale-while-revalidate) is a fully dedicated React Hooks library for remote data fetching. So basically, this is what SWR does:
- First returns the data from cache (stale)
- Then sends the fetch request (revalidate).
- Finally comes with the up-to-date data again.
Why Suspense?
Because Suspense will definitely help you to maintain a consistent UI in the face of asynchronous dependencies. Something I prefer to call on-demand loaded React components.
All you need is to:
- Set the suspense: true as one of the useSWR hook options.
- Wrap your on demand route component inside of its own suspense component.
import React, { Suspense } from "react";
import ReactDOM from "react-dom";
import useSWR from "swr";
const App = () => {
return (
<Suspense fallback={<FallbackProgress />}>
<Page />
</Suspense>
);
};
const FallbackProgress = () => <div>Loading...</div>;
const Page = () => {
const { data } = useSWR(
"https://jsonplaceholder.typicode.com/Todos/1",
(req) => fetch(req).then((res) => res.json()),
{ suspense: true }
);
return <div>{data.title}</div>;
};
const rootElement = document.getElementById("root");
ReactDOM.createRoot(rootElement).render(<App />);
The whole thing put together
- Github – components/States/States.js
import React, { Suspense, lazy } from "react";
import { Progress } from "components";
const StatesLazy = lazy(() => import("./States.lazy"));
const States = () => (
// Progress Component contains `data-testid="id-request-progress"`
<Suspense fallback={<Progress />}>
<div data-testid="id-states-lazy">
<StatesLazy />
</div>
</Suspense>
);
export default States;
- Github – components/States/States.lazy.js
import React, { useEffect } from "react";
import Container from "@material-ui/core/Container";
import Divider from "@material-ui/core/Divider";
import { Link } from "react-router-dom";
import Typography from "@material-ui/core/Typography";
import { Redirect, useHistory } from "react-router-dom";
import _get from "lodash.get";
import useSWR from "swr";
import {
useSearchValueDispatch,
useSearchValueState,
} from "context/SearchValue";
import { endpoints } from "config";
import { searchUtil } from "utils";
import { csv2objFetcherService } from "services";
import { ContentCard, ContentMessage } from "components";
import useStyles from "./States.lazy.style";
const StatesLazy = () => {
const classes = useStyles();
const history = useHistory();
const { searchValue } = useSearchValueState();
const dispatch = useSearchValueDispatch();
const requestURLConst = "for=state:*&DATE_CODE=1";
const { data } = useSWR(
`${endpoints.mainURL}${requestURLConst}`,
csv2objFetcherService,
{ suspense: true }
);
const response = {
status: _get(data, "status", 200),
data: _get(data, "data", []),
error: _get(data, "error", ""),
};
useEffect(() => {
dispatch({ type: "setSearchValueReducer", payload: "" });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (response.status !== 200 && response.error !== "") {
return (
<div data-testid="id-states-response-error">
<Redirect
to={{
pathname: history.location.pathname,
state: { status: response.status, error: response.error },
}}
/>
</div>
);
}
const searchResults = searchUtil(response.data, searchValue);
if (searchResults && searchResults.length === 0) {
return (
<div data-testid="id-states-no-search-results">
<ContentMessage
type="message"
title="No Results Found!"
description="Let's ask again."
/>
</div>
);
}
return (
<Container
data-testid="id-states-container"
className={classes.root}
maxWidth="md"
>
<Typography variant="h1" className={classes.title}>
States
</Typography>
<Divider className={classes.divider} />
{searchResults.map(
(state) =>
state.NAME &&
state.state && (
<Link
// Key
key={Number(state.state)}
// Rest of the Props
className={classes.link}
to={`${Number(state.state)}/counties`}
>
<ContentCard
// Key
key={Number(state.state)}
// Rest of the Props
density={state.DENSITY}
population={state.POP}
title={state.NAME}
/>
</Link>
)
)}
</Container>
);
};
export default StatesLazy;
Wrapping up
As long as there is a child component using promises or acting asynchronous inside the Suspense component, it will be replaced by the component defined in the fallback prop.