Skip to content

Commit 13dca77

Browse files
committed
transitions
1 parent 5510ea8 commit 13dca77

File tree

8 files changed

+145
-0
lines changed

8 files changed

+145
-0
lines changed

lessons/05-performance-optimizations/A-where-react-can-be-slow.md

Whitespace-only changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
You know how your remote has those buttons for streaming services on it? Have you ever accidentally pressed "Uncle Pete's Streaming Service" instead of "Netflix" and it takes forever to load and you're held hostage to the unoptimized loading of their crappy app? Really frustrating, right?
2+
3+
Now think of that happening on a website you've been on – you have several options to pick from and you accidentally chosoe the wrong one, and now you have to wait for the UI to be interactive again for you to be able to correct your mistake. Really annoying, especially on slower sites, slower connections and/or slower devices.
4+
5+
This is what transitions are for in React. They allow you to keep UIs interactive while loading is happening behind the scene.
6+
7+
We're going to build a little app that shows sports scores with an intentionally slow API. We're going to make it so users who accidentally select the wrong score to look at can still select a new game even while others are loading in the background.
8+
9+
Most React apps in the past would have just locked the user out of being able to make new choices while data is loading - it's the easy way to code this up. But with the `useTransition` hook, this became pretty easy to do. Let's do it.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
We're making a little scoreboard project, let's familiar ourselves with it.
2+
3+
[Start with the transitions][folder]. The complete solution is in the [transitions-complete][complete] folder if you need to reference it.
4+
5+
- Run `npm install`
6+
- It's a plain ol' Vite project. No server side anything. You can do transitions with servers, we just don't need to.
7+
- There's plain little Node.js server that just allow you to request the score of games 1 through 5. It doesn't do anything else.
8+
- Run `npm run dev:server` in one terminal and `npm run dev:client` in another. One runs Vite and one runs the Node.js server.
9+
- Vite will proxy the Node.js server from port 3000 to port 5173 so you can call localhost:5173/score?game=1 - all same origin
10+
- Everything else is a pretty standard React project.
11+
12+
[folder]:
13+
[complete]:
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
Let's first make this "wrong" first - the problem where the UI locks up during transitional times.
2+
3+
Let's make a Score.jsx file
4+
5+
```javascript
6+
export default function Score({ isPending, home, away, game }) {
7+
return (
8+
<div className="score">
9+
<div>
10+
<h2>HOME {game}</h2>
11+
<h3>{isPending ? "" : home}</h3>
12+
</div>
13+
<div>
14+
<h2>AWAY {game}</h2>
15+
<h3>{isPending ? "" : away}</h3>
16+
</div>
17+
</div>
18+
);
19+
}
20+
```
21+
22+
- Key here is that if the score is pending, we show a dash. That will visually indicate to the user we haven't loaded it yet.
23+
24+
Okay, let's make a quick function fetch from the API. Make a file called getScore.js
25+
26+
```javascript
27+
export default async function getScore(game) {
28+
const response = await fetch("/score?game=" + game);
29+
const score = await response.json();
30+
return score;
31+
}
32+
```
33+
34+
No surprises here.
35+
36+
Let's go modify our App.jsx. Put this in there
37+
38+
```javascript
39+
import { useState, useEffect } from "react";
40+
import Score from "./Score";
41+
import getScore from "./getScore";
42+
43+
export default function App() {
44+
const [isPending, setIsPending] = useState(true);
45+
const [game, setGame] = useState(1);
46+
const [score, setScore] = useState({ home: "", away: "" });
47+
48+
async function getNewScore(game) {
49+
setIsPending(true);
50+
setGame(game);
51+
const newScore = await getScore(game);
52+
setScore(newScore);
53+
setIsPending(false);
54+
}
55+
56+
useEffect(() => {
57+
getNewScore(game);
58+
}, []);
59+
60+
return (
61+
<div className="app">
62+
<h1>Game {game}</h1>
63+
<select
64+
disabled={isPending}
65+
onChange={(e) => {
66+
getNewScore(e.target.value);
67+
}}
68+
>
69+
<option value={1}>Game 1</option>
70+
<option value={2}>Game 2</option>
71+
<option value={3}>Game 3</option>
72+
<option value={4}>Game 4</option>
73+
<option value={5}>Game 5</option>
74+
</select>
75+
<div className={`loading-container ${isPending ? "loading" : ""}`}>
76+
{" "}
77+
<span className="spinner">⚽️</span>
78+
</div>
79+
<div>
80+
<Score
81+
isPending={isPending}
82+
game={game}
83+
home={score.home}
84+
away={score.away}
85+
/>
86+
</div>
87+
</div>
88+
);
89+
}
90+
```
91+
92+
- So this works, and this is how most people would have coded this - just wait until the API request finishes.
93+
- Why do we need to disable the select while stuff is loading? Because if we don't people can make multiple requests in that time, and they'll return in a jumbled order and it'll freak people out. We have to make sure it finsihes it first so we don't have a UI that's responding to old requests.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Luckily the useTransition code is only slightly more complex.
2+
3+
```javascript
4+
// add useTransition at top
5+
import { useState, useTransition, useEffect } from "react";
6+
7+
// replace isPending useState
8+
const [isPending, startTransition] = useTransition();
9+
10+
// replace getNewScore
11+
async function getNewScore(game) {
12+
setGame(game);
13+
startTransition(async () => {
14+
const newScore = await getScore(game);
15+
startTransition(() => {
16+
setScore(newScore);
17+
});
18+
});
19+
}
20+
```
21+
22+
That's it!
23+
24+
- startTransition gives you back a isPending flag just like we had before.
25+
- It now gives us a function to call that we're starting our UI transiton. We're essentially signalling to React that we're starting some sort of transition (in our case, an API request and a subsequent UI render) that we could elect to interrupt with another transition.
26+
- Why two `startTransition` calls? I struggled with this as well. It's in the React docs. It's because in theory React updates aren't instant. If you're a Facebook-sized app, a React update can actually be on the order tens if not hundreds of milliseconds which is forever in terms of code execution, and this means it needs to be captured this way so that the use could in theory interrupt it after the API request has returned but while React is re-rendering.
27+
- I'll say _in this particular case_ it's unnecessary, our app is tiny. But it's to make sure each transition is captured an atomic bit that can be interrupted.

lessons/07-deferred-values/A-what-are-deferred-values.md

Whitespace-only changes.

lessons/08-optimistic-values/A-what-are-optimistic-values.md

Whitespace-only changes.

lessons/09-wrap-up/A-congrats.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
title: "Congrats!"
3+
---

0 commit comments

Comments
 (0)