Juntao Qiu - Advanced Data Fetching Patterns in React - 2024
Juntao Qiu - Advanced Data Fetching Patterns in React - 2024
Patterns in React
Fast, User-Friendly Data Fetching for
Developers
Juntao Qiu
This book is for sale at
http://leanpub.com/react-data-fetching-patterns
Preface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Chapter 1: Introduction . . . . . . . . . . . . . . . . . . . . . 2
Setting up the environment . . . . . . . . . . . . . . . . . 4
Setting up the backend service . . . . . . . . . . . . . . . . 7
This tutorial aims to equip you with the knowledge and tools to
navigate the complexities of network requests in React and other
frontend frameworks, enhancing both your understanding and
practical skills.
The page we’re going to build is a Home page in an imaginary social
media website. It doesn’t do much but showing a user their home
page when then log in.
Chapter 1: Introduction 4
1 function App() {
2 return (
3 <div className="max-w-3xl m-auto my-4 text-slate-80\
4 0">
5 <h1 className="text-4xl py-4 mb-4 tracking-wider \
6 font-bold">Profile</h1>
7 </div>
8 );
9 }
10
11 export default App;
Figure 2. tailwind.config.js
1 @tailwind base;
2 @tailwind components;
3 @tailwind utilities;
I prefer to make the background a bit gray, so I’ll add the following
line in src/index.css
Figure 4. src/index.css
1 body {
2 background-color: #fefefe;
3 }
1 cd mock-server-network-react-tutorial
2 yarn start
1 https://github.com/abruzzi/mock-server-network-react-tutorial
Chapter 1: Introduction 8
And if you try to access the one of the following API endpoint:
1 curl http://localhost:1573/users/u1
1 {
2 "id": "u1",
3 "name": "Juntao Qiu",
4 "bio": "Developer, Educator, Author",
5 "interests": [
6 "Technology",
7 "Outdoors",
8 "Travel"
9 ]
10 }
1 curl http://localhost:1573/users/u1
1 {
2 "id": "u1",
3 "name": "Juntao Qiu",
4 "bio": "Developer, Educator, Author",
5 "interests": [
6 "Technology",
7 "Outdoors",
8 "Travel"
9 ]
10 }
Figure 8. App.tsx
1 import { Profile } from "./src/profile.tsx";
2
3 function App() {
4 return (
5 <div>
6 <h1>Profile</h1>
7 <div>
8 <Profile id="u1" />
9 </div>
10 </div>
11 );
12 }
13
14 export default App;
1 https://web.dev/articles/rendering-on-the-web
2 https://web.dev/articles/client-side-rendering-of-html-and-interactivity
3 https://developer.mozilla.org/en-US/docs/Web/Performance/How_browsers_work
Chapter 2: Basics of Data Fetching in React 14
35 );
36 };
For brevity, I’m omitting Tailwind CSS from the code snippets.
You can view the full styled components in the corresponding
code repository.
9 );
10 setLoading(false);
11 setUsers(data);
12 };
13
14 fetchFriends();
15 }, [id]);
16
17 if(loading) {
18 return <div>Loading...</div>
19 }
20
21 return (
22 <div>
23 <h2>Friends</h2>
24 <div>
25 {users.map((user) => (
26 <div>
27 <img
28 src={`https://i.pravatar.cc/150?u=${user.id\
29 }`}
30 alt={`User ${user.name} avatar`}
31 />
32 <span>{user.name}</span>
33 </div>
34 ))}
35 </div>
36 </div>
37 );
38 };
39
40 export { Friends };
The process involves three renderings. After the first render, the
page displays a loading... message while initiating the /users/u1
request. When the server responds, the About section is displayed.
As Friends renders, lacking available data, it shows a loading...
message in its section and sends out the /users/u1/friends request.
Upon receiving this data, the third rendering occurs.
Chapter 3: Fetching Resources in Parallel 21
Over time, as the component tree grows, the page becomes increas-
ingly slower.
However, one might wonder if initiating data fetching simultane-
ously could mitigate this wait time.
Now, the total wait time is reduced to max(1.5, 1.5) = 1.5 seconds,
a significant improvement:
The only remaining issue is the potential wait for the slower request
in extreme cases. We’ll accept this limitation for now and explore
solutions in subsequent chapters.
Chapter 3: Fetching Resources in Parallel 26
Request Dependency
Parallel requests expedite the loading of independent data. How-
ever, some requests depend on others. For example, we might need
to fetch user information first and use the interests array from
the response to retrieve recommended feeds for the user. This
sequential dependency necessitates a return to the initial approach.
In the Feeds component, we define loading, error, and data states,
and useEffect initiates network fetching after the initial render:
Chapter 3: Fetching Resources in Parallel 27
1 return (
2 <>
3 {user && <About user={user} />}
4 <Friends users={friends} />
5 {user && <Feeds category={user.interests[0]} />}
6 </>
7 );
Initially, About and Friends load, and as soon as the user data is
available, we use interests[0] to fetch feeds, potentially taking
another second. The overall wait time amounts to max(1.5, 1.5)
+ 1 = 2.5 seconds.
The feeds request must wait for the completion of the previous
two requests, displaying a large spinner in the interim. While
this solution is functional, we must consider the runtime data
requirements for each specific user.
By mastering parallel requests and managing dependencies in
network calls, this chapter sets the foundation for building faster
and more responsive React applications. Join us as we continue to
navigate the intricate world of advanced network patterns in React.
In the next chapter, we explore further optimization strategies and
delve into more complex scenarios of network fetching in React.
Chapter 4: Optimizing
Friend List Interactions
This chapter focuses on enhancing the user experience in React
applications by implementing a detailed user profile popover.
It explores the integration of external UI libraries like NextUI
for building interactive features and discusses efficient data
fetching strategies.
To maintain focus on our main topic, I’ll skip the detailed imple-
mentation of the popover itself. Instead, we’ll utilize components
from nextui for the popover behavior and UserDetailCard.
11 </div>
12 </NextUIProvider>
13 );
14 }
The Brief component accepts a User object and renders its details:
Chapter 4: Optimizing Friend List Interactions 35
Implementing UserDetailCard
Component (Fetching Data)
UserDetailCard is designed to fetch and display user details. The
user detail includes:
Chapter 4: Optimizing Friend List Interactions 36
We use our reusable get function to fetch these details from the
/users/<id>/details endpoint:
23 return (
24 <Card shadow="none">
25 <CardHeader>
26 <div>
27 <Avatar
28 isBordered
29 radius="full"
30 size="md"
31 src={`https://i.pravatar.cc/150?u=${detail.id\
32 }`}
33 />
34 <div>
35 <h4>{detail.name}</h4>
36 <p>{detail.twitter}</p>
37 </div>
38 </div>
39 </CardHeader>
40 <CardBody>
41 <p>{detail.bio}</p>
42 </CardBody>
43 <CardFooter>
44 <div>
45 <p>
46 <a href={detail.homepage}>{detail.homepage}</\
47 a>
48 </p>
49 </div>
50 </CardFooter>
51 </Card>
52 );
53 }
54
55 export default UserDetailCard;
next chapter.
With the introduction of advanced UI elements and thoughtful
data fetching strategies, this chapter elevates the user experience in
React applications, paving the way for more engaging and efficient
frontend designs. In the next chapter, we’ll dive into code splitting
and lazy load to reduce the initial load, that also the foundation of
React concurrent we’ll learn later.
Chapter 5: Leveraging
Lazy Load and Suspense
in React
Chapter 5 of the ‘Advanced Network Patterns in React’ tutorial
explores the concepts of Lazy Loading and React Suspense for
optimizing performance. It demonstrates how to dynamically
load components only when they are required, reducing initial
load times and improving user experience.
At the end of the previous chapter, we noticed that the page now
has more bytes to load initially, which might not fair for user who
don’t hover on a Friend component - they still need to pay for the
extra network requests and JavaScript bundles.
We could delay such extra (not immediate useful) content into
another request as late as possible (maybe never if the users
don’t ask). For example, we could split UserDetailCard (and its
dependency) into a separate JavaScript bundle and load it whenever
the user hover on it.
Chapter 5: Leveraging Lazy Load and Suspense in React 41
Let’s see how to implement it in React with lazy load and suspense.
19 <Suspense fallback={<div>Loading...</div>}>
20 <UserDetailCard id={user.id} />
21 </Suspense>
22 </PopoverContent>
23 </Popover>
24 );
25 };
1 <Suspense fallback={<div>Loading...</div>}>
2 <UserDetailCard id={user.id} />
3 </Suspense>
Chapter 5: Leveraging Lazy Load and Suspense in React 45
Note in the chart above, the thin slice on the left hand side it the
UserDetailCard, while the big one on the right is everything else.
And if we look closely we’ll find the biggest one is framer-motion
- a package that adding the animation in React - shipped within
NextUI. Obviously we don’t really need animation for everything,
it only used when the popover shows up.
We could further split the Friend into a separate bundle with
NextUI components, and leave the index lightweight.
So firstly we don’t import Friend in Friends, instead we lazy load
it with suspense:
Chapter 5: Leveraging Lazy Load and Suspense in React 47
1 //...
2 const UserDetailCard = React.lazy(() => import("./user-de\
3 tail-card.tsx"));
4
5 const Friend = ({ user }: { user: User }) => {
6 return (
7 <NextUIProvider>
8 <Popover placement="bottom" showArrow offset={10}>
9 <PopoverTrigger>
10 <button>
11 <Brief user={user} />
12 </button>
13 </PopoverTrigger>
14 <PopoverContent>
15 <Suspense fallback={<div>Loading...</div>}>
16 <UserDetailCard id={user.id} />
17 </Suspense>
18 </PopoverContent>
19 </Popover>
20 </NextUIProvider>
21 );
22 };
23
24 export default Friend;
With these updates, our new analysis reveals that we have three
distinct bundles: UserDetail, Friend, and Profile.
Chapter 5: Leveraging Lazy Load and Suspense in React 49
In the last chapter, we explored how lazy loading and the Suspense
API can defer the loading of larger, performance-impacting chunks,
thereby enhancing the initial load speed and user experience.
This approach, leveraging lazy loading, successfully reduced un-
necessary requests and improved overall performance. Yet, as
we wrapped up the chapter, a question arose: could we further
optimize this?
Now, in this chapter, our focus shifts to employing the preload
technique to accelerate user interactions even more. Our specific
goal is to boost performance when users access the UserDetail.
Chapter 6: Prefetching Techniques in React Applications 52
Introducing SWR
We’re introducing a package named SWR in this chapter, which
will streamline our data fetching process and implement a preload
feature.
Here, useSWR fetches and returns user data, handling loading states
and errors seamlessly.
Chapter 6: Prefetching Techniques in React Applications 54
12 </button>
13 </PopoverTrigger>
14 {/* existing logic */}
15 </Popover>
16 );
17 };
Introducing Next.js
If you’re not familiar with Next.js, I recommend going through its
dashboard tutorial1 . It’s beneficial to follow along with the tutorial,
experiment with it, and then return here.
Next.js is an open-source web development framework built on
Node.js, designed to simplify the building of server-rendered Re-
act applications. Created by Vercel (formerly Zeit), it aims to
streamline the process of building performant, scalable, and user-
friendly web applications. It enhances the developer experience
with features like automatic code splitting, server-side rendering,
static site generation, and built-in CSS support. In this chapter,
we’ll focus on its app router feature for SSR, which functions
similarly to client-side React components.
To start, we’ll create a dynamic route – app/user/[id]/page.tsx.
This path signifies a dynamic route within the app directory in
Next.js. For instance, accessing /user/123 renders the page.tsx
component for the user with ID 123, changing content based on
the URL parameter.
1 https://nextjs.org/learn/dashboard-app
Chapter 7: Introducing Server Side Rendering 61
2 https://nextjs.org/docs/app/building-your-application/routing
3 https://nextjs.org/docs/app/building-your-application/routing/pages-and-layouts
Chapter 7: Introducing Server Side Rendering 62
1 interface PageProps {
2 params: {
3 id: string;
4 };
5 }
17 setLoading(false);
18 setFeeds(data);
19 };
20
21 fetchFeeds();
22 }, [category]);
23
24 if (loading) {
25 return <div>Loading...</div>;
26 }
27
28 return (
29 <div>
30 <h2>Your Feeds</h2>
31 <div>
32 {feeds.map((feed) => (
33 <>
34 <h3>{feed.title}</h3>
35 <p>{feed.description}</p>
36 </>
37 ))}
38 </div>
39 </div>
40 );
41 };
42
43 export { Feeds };
Though the initial load may take slightly longer, subsequent op-
erations become more responsive, as no additional data loading is
required. This approach enhances the user experience and allows
for various caching levels, speeding up future data requests.
In this chapter, we explore Server-Side Rendering (SSR) and its
integration with React Server Components. We delve into how SSR
improves initial page load times, making React applications more
performant and user-friendly. Up next: Static Site Generation.
With key insights from this chapter, we pave the way to explore
the synergy between SSR and React Server Components, setting
the stage for the next topic: Static Site Generation.
Chapter 8: Introducing
Static Site Generation
Chapter 8 explores Static Site Generation (SSG) in React,
emphasizing how to optimize webpages at build time for
enhanced performance and user experience. It differentiates
between runtime and build-time data fetching, providing prac-
tical examples using Next.js.
Chapter highlights:
Chapter highlights:
In this chapter, we will look into the loading indicator, there are two
common ways of showing the users that something is happening
and we can not show the data right away: skeleton and spinner.
A skeleton component is a user interface design used to indicate
data loading, where a placeholder mimicking the actual content
layout is displayed. These are usually grey blocks or lines that show
Chapter 9: Optimizing User Experience with Skeleton Loading in 73
React
where and how the final content, like text or images, will appear.
The skeleton component enhances the user experience by reducing
the element of surprise during loading, providing a preview of the
content’s structure.
1 function AboutSkeleton() {
2 return (
3 <div className="flex flex-row gap-2 pb-4 items-center\
4 animate-pulse">
5 <div>
6 <div className="w-12 h-12 rounded-full animate-pu\
7 lse bg-slate-300" />
8 </div>
9 <div className="flex flex-col gap-2">
10 <div className="text-2xl font-bold w-20 h-6 bg-sl\
11 ate-200" />
12 <p className="text-xs w-24 h-2 bg-slate-200" />
13 </div>
14 </div>
15 );
16 }
Chapter highlights
This approach wasn’t feasible until the release of React 18, which
expanded the use of Suspense beyond just code splitting.
It’s important to note that this new application of Suspense is
still experimental and not yet widely considered production-ready.
However, libraries like SWR and React Query, and frameworks
such as Next.js, are already experimenting with it.
Let’s examine how to implement data-fetching with Suspense.
Streaming in Next.js
Next.js documentation explains streaming as a technique to pro-
gressively render UI from the server. It splits work into chunks
streamed to the client as they’re ready. This allows for immediate
rendering of parts of the page.
In Next.js, streaming can be achieved through:
Chapter highlights:
1 'use client'
2
3 const Gallery = dynamic(() => import("./gallery"));
4
5 const App = () => {
6 return <Gallery />
7 }
1 'use client'
2
3 const Gallery = dynamic(() => import("./gallery"), {
4 loading: () => <GallerySkeleton />
5 });
6
7 const App = () => {
8 return <Gallery />
9 }
15 <PopoverContent>
16 <UserDetailCard id={user.id} />
17 </PopoverContent>
18 </Popover>
19 </NextUIProvider>
20 );
21 };
Preload in Next.js
Preloading can be used to fetch data in advance, such as when a
user hovers over a button:
Figure 71. components/friend.tsx
1 "use client";
2
3 const UserDetailCard = dynamic(() => import("./user-detai\
4 l-card"));
5
6 export const Friend = ({ user }: { user: User }) => {
7 const handleHover = () => {
8 preload(user.id);
9 };
10
11 return (
12 <NextUIProvider>
Chapter 11: Lazy Load, Dynamic Import, and Preload in Next.js 94
The preload function fetches the user’s details when the user
hovers over the Friend component. This is accomplished by
triggering the preload function on the onMouseEnter event of the
button element. When the mouse pointer enters the button’s area,
getUserDetail(id) is called, and it fetches the details of the user
associated with the Friend component.
Chapter 11: Lazy Load, Dynamic Import, and Preload in Next.js 95
Note here in line 8 above, the void operator1 evaluates the ex-
pression (getUserDetail(id)) and then returns undefined, which
is handy if we want to execute a function but don’t care of the
return value.
14 };
1 'use client';
2
3 async function Friends({ id }: { id: string }) {
4 const friends = await getFriends(id);
5
6 return (
7 <NextUIProvider>
8 <div>
9 <h2>Friends</h2>
10 {/**/}
11 </div>
12 </NextUIProvider>
13 );
14 }