
Lesson Description
The "Loading Scores without Transitions" Lesson is part of the full, Intermediate React, v6 course featured in this preview video. Here's what you'd learn in this lesson:
Brian walks through building a React app without transitions to display scores. He also demonstrates how long loading times negatively impact user experience when transitions are not used.
Transcript from the "Loading Scores without Transitions" Lesson
[00:00:00]
>> Brian Holt: Okay, so let's make this wrong first. So basically, we're gonna do exactly the problem that I just described to you, and I'll say that this is the way that most React apps just get coded up, right? The user makes a decision to do something, and you just do everything, and you make the user wait until you've loaded everything and then you show them everything.
[00:00:22]
That's just what we've done for a very long time, okay? So let's go ahead and code this together. Let's make a Score.jsx file first.
>> Brian Holt: So we're gonna have a const loadingUrl, and this is just an image that we like. It's a loading image that we can show people, images/loading.webp.
[00:00:47]
If you're not familiar with dot webp, it's an image format that's a bit more efficient than JPEG or PNG. It's fairly widely compatible with basically every browser now, but for a long time it wasn't, which is why it's probably less common. I think it's from Google. Is anyone gonna correct me on that?
[00:01:06]
I'm sure someone in chat is furiously typing at the moment. But I think it's from Google. Export default,
>> Brian Holt: Function Score, and this is gonna accept quite a few things. So isPending, home, away, awayName, homeName, awayImage, and homeImage, something like that.
>> Brian Holt: I'll just hide this for the moment.
[00:01:49]
We don't need that. So, I'm gonna answer this question cuz I imagine someone's thinking about it. You could have just done like an options object as opposed to naming these all one by one. I like this, I prefer this, it's a bit more explicit. In theory, it makes it fit better as well with memoization.
[00:02:07]
I actually don't really care about that. I just like that this component is very explicit about it requires these things to work. It's kind of self-documenting code in that way. But it's an opinion, you could also just totally have said something like options and just this would all be put inside that options or something like that.
[00:02:28]
We're just gonna return markup here. So div className = "score", and just div here, h2 we'll put, and then we'll say isPending. And if it's pending, we're gonna say it's HOME, otherwise, we'll say it's the homeName.
>> Brian Holt: Okay, and then we're gonna do something pretty similar with an h3, isPending.
[00:03:03]
>> Brian Holt: And this will be the score. So just like a dash or something like that, otherwise it's gonna be the home score. The away one's gonna look pretty much the same, so you can just kinda copy and paste here and say AWAY, awayName, and Away.
>> Brian Holt: And underneath that, in each one of these, we also need an image.
[00:03:27]
So image source equals isPending, and if it's pending, then we're gonna do the loadingurl. We'll show the placeholder image, otherwise, we'll show the actual homeImage, and the alt is home team logo. I'm sure you could get more descriptive than that, we're not going to right now. Okay, same thing down here.
[00:03:50]
It's just awayImage and away team logo. Really, the thing to take away from here is writing some of this is kind of burdensome of like, is it loading? Is it not loading? It's kind of a pain in the butt. Not that this is necessarily gonna solve that, but, yeah, we'll start taking advantage of this here in just a second.
[00:04:13]
Okay, let's make a quick function in here as well. We'll call it just, sorry, not a function, but rather a file, and we'll just call it getScore.js. And we'll just make an export default async function, getScore(game). And let's say const response = await fetch( "/score?game=" +game), whatever. Yeah, this is just gonna be a number, right?
[00:04:51]
And then const score equals, await response.json, and return score. Okay, cool? Let's go modify App.jsx. And at the top here, we're going to import useState and useEffect from "react". Import Score from ./Score, and import getScore from getScore. I mean, frankly, it would be making a hook out of getScore.
[00:05:43]
If I had thought about this more in advance, I probably would have done this, but instead of having an effect directly in our App.jsx, we could have it kind of encapsulated nicely into a hook. So feel free to refactor that, could be a fun exercise.
>> Brian Holt: Okay, and then const isPending and setIsPending = useState, and it's gonna be true at first, right, cuz we won't have loaded the score.
[00:06:17]
Const game and setGame equals useState(1). I index these one to 7, by the way, it's not indexed up by 0. And then const score setScore,
>> Brian Holt: Equals useState, and I made this one an object. So home will be blah and away will be blah. In theory, this won't matter because you saw in our score component that you won't display it if it's pending.
[00:07:00]
>> Brian Holt: Okay, async function getNewScore(game). Okay, the user requests a new score, what are we gonna do first? Well, we have this isPending flag, so we probably wanna mark the UI as unresponsive right away. So let's do that. We're gonna say setIsPending(true), okay? Then you wanna set the game to be the game.
[00:07:31]
Const newScore is assigned await getScore(game), and setScore(newScore), and setIsPending(false). So very procedural here. Mark yourself as loading, change the game. [LAUGH] Change the game that you are loading. Go load what actually you want. Load that into memory here, and then finally you're gonna tell it to re-render and show the score.
[00:08:05]
Key here is that you're gonna await this call, cuz if you recall, this is going to take at least five seconds to load. UseEffect,
>> Brian Holt: And here you're just gonna say getNewScore(game). So I had this coded this way, where this effect never runs again. Again, reading my code again, you could actually just say that this is dependent on game, and then you could have this run every time game changes.
[00:08:32]
Either one of these are fine. We're just gonna follow my notes, but just know that there's many ways to do this.
>> Brian Holt: ClassName= "app", okay? And then we're gonna do an h1 here, Game{game}, and we're gonna do a select, and then we're gonna disable it if it isPending, why?
[00:09:03]
Well, imagine that you have a user that does exactly what we're talking about. They choose one, and then they choose another. And let's say the first request is slower than the second request. For many network conditions, that could be true. You would actually end up with the first request because it's gonna be the last one returned that matters, right?
[00:09:22]
Because this doesn't account for any sort of order. It's just gonna say, cool, this is what it is now, right? So in order to avoid that potential kind of race condition, we're just gonna disable it so that the user can't choose again. OnChange= {e=>, and then we're gonna say getNewScore(e.target.value)}.
[00:09:49]
Now, again, if you did this with game up here, instead of calling this directly, what you would do is you would just call setGame and then just let everything kind of cascade that way. Honestly, no real preference either way, both of these are explicit enough for me. Okay, so getNewScore, we did that.
[00:10:09]
And then we're just gonna have some options here. Value = {1}, and then we're gonna say Game 1. And you could get fancy and have this do a range or something like that. I am just gonna do 2. I'm gonna make this really dumb and just explicitly write them out.
[00:10:33]
So now we have our options
>> Brian Holt: Okay, and then we're gonna do <div className= "loading-container">. So we're gonna show a nice little loading spinner here. So loading-container, and then I'll make sure this is a template string. So not double quotes here, but template string, so that we can do something like this, isPending.
[00:11:11]
>> Brian Holt: Loading class, and if it's not there, then don't put anything. So why this? This will add the loading class specifically to this div, which using the CSS, it'll make it visible. And then we're just gonna put a span here, className="spinner". Feel free to put any emoji you want here, I'll just put a soccer ball.
[00:11:39]
But anything you wanna make spin, just put it right there. Another div, and then we're gonna put a score, and then we're gonna make sure we add all of our state into this. IsPending={isPending}. HomeImage equals, isn't it score? Where do I keep that? Isn't it score? It's up in here.
[00:12:03]
So score.homeImage, and homeName equals score.homeName and awayImage. The other advantage of doing it this way is, I guess you can get a TypeScript to check it in an object as well, but the autocompletion is really nice when it has a very explicit API. You could get real lazy if you wanted to and you could just ...score and that would just work.
[00:12:38]
And I am tempted to right now, but I am not going to. Score.awayImage. Those kind of implicit, yeah, it's really easy to write and it's really hard to maintain.
>> Brian Holt: Okay, home=awayScore.home) and away=(score.away). The links that developers will go to to just avoid a couple of keystrokes is phenomenal.
[00:13:13]
Okay, so I believe we should be able to come over here to React Transitions here. What did we break first? Dot slash getScore, this is not a module. So Line 3 here, getScore. And why did I call this getNewScore? It's just getScore, like that. So getScore from ./score, and then Line 13 should have been getScore.
[00:13:45]
If I come in here to completed, and we look at transitions, and we look at source, and we look at style, and we look at raw.
>> Brian Holt: And I say blah, that might be it.
>> Brian Holt: So, yeah, we were in app, we finished all of that, and we are going to go look at the app now.
[00:14:13]
And so, I'll just refresh here, you can see this takes a long time for it to load. We see our placeholder images and then we can see that we have the 404 not found versus the 500 server errors. And then if I wanna look at whatever Game 5 is, it loads in a nice loading state, but I can't change this, right?
[00:14:32]
So if I clicked Game 5 and I really wanted Game 6, you're stuck. You are like, you gotta wait that five seconds, and users are gonna get frustrated with that. I coded this and I'm already frustrated by this. So this is the way that we typically had done it.
[00:14:47]
Normally, a five second API call is ridiculous. If you have a five second API call, that's your problem, not your transitions, probably, but transitions can help with this kind of problem. Maybe you are doing something like calling an LLM, which just necessitates time, right? If you're calling Claude with thinking, it takes 12 seconds for that to come back.
[00:15:11]
So some of this things you can't avoid. And that's where this can be really helpful, is this transition API helps your UI stay interactable, and you kind of take advantage of the React schedule to kind of handle all these sort of things. Cuz it'll guarantee you whatever the one you call last is actually the state that it's going to observe.
[00:15:28]
So that problem of like, I called this but the other one came back first, doesn't become important, which is really cool as well.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops