Advanced Remix

Skeletons Solution

Kent C. Dodds

Kent C. Dodds

Professional Trainer
Advanced Remix

Check out a free preview of the full Advanced Remix course

The "Skeletons Solution" Lesson is part of the full, Advanced Remix course featured in this preview video. Here's what you'd learn in this lesson:

Kent codes the solution to the Skeletons exercise.


Transcript from the "Skeletons Solution" Lesson

>> Okay, welcome back, exercise eight, super fun, where we're going to show a fancy loading skeleton for our users. And actually, yeah, let's just open it right up here. So we need to determine when we show the skeleton, versus when we show the outlet. So I'm gonna start down here, actually, and I'm going to say true, we'll fill that in with an actual variable later, then render the skeleton.

Skeleton, how do you spell skeleton? CustomerSkeleton, there we go. Otherwise, we render the outlet. And in the case of the CustomerSkeleton, it also wants a name and an email. So we provide the name of Kent, and the email of I mean, you can send an email there, but I don't think it goes to anybody.

So with that, we can see what our skeleton looks like, as soon as our really slow backend is done. I've rendered two Outlet in there. Actually, fun fact, you can render multiple outlets [LAUGH]. And it will do exactly what you think it will do. It will render that outlet multiple times.

I can't imagine why you'd want to do that, but, I mean, the world is your oyster, so [LAUGH]. Anyway, this is what we get, the customer email, customer name. And then the invoices with the fancy little loading state. So now all we need to do is figure out, okay, when do we show this?

And what is the email and username and name of the customer that we selected? Now, if you're like me, your first impulse is to think, well, I'll just have some state that I manage. That as soon as I click on this, that just says last clicked customer, or something like that.

That's how I would have implemented this before. Honestly, Ryan, taught me better [LAUGH]. Because the browser's already managing some state for you in the history API, if you say history.state, there's some state in here. Now, some of this stuff is implementation detail stuff for the router. But we can actually access this via a fancy little prop on links and NavLinks, which are just basically links that are kinda special.

So with that now, whichever link we click on is going to update the history.state to have a customer prop on it. So if we come over here, and then we click on this link, and then we look at history.state, then here's usr, this user state. So React Router is managing that for us.

But there's our customer, that's the customer that was clicked on. So I don't have to manage state, hurray, thank you, Remix, again, for keeping state out of my code. So I'm just literally saying, here's the customer that I'm clicking on when I click on this NavLink. And so from that, I can say who's the next customer, and that's the customer I should be sending to this name and email.

So we gotta get that state out of React Router, out of Remix. So we're gonna get our loading customer, and actually let's initialize that to undefined. And then if the transition.location, hold on, we forgot to bring in useTransition. UseTransition, again, from Remix, not React Router, or not React, transition.

Okay, so now if the location has state, then we'll say our loadingCustomer = transition.location.state.customer. Now, TypeScript is not gonna be happy about this, cuz this cannot be typed as anything else. TypeScript cannot know what could go in there. And frankly, we can't either, we can't be sure, somebody could be coming to this from something else completely.

And we have no way of knowing for sure. So it might be good to add some type safety here for now. What I mean by that is a runtime type assertion to assert that yes, this is indeed a customer. For now, we're just gonna shut up TypeScript by saying as any.

Cool, so now we have our loadingCustomer. And this also is gonna be typed as any. This is not a TypeScript workshop. So we're gonna skip over making that happier. So we'll take that loadingCustomer, and we'll say, showSkeleton =, I guess just Boolean(loadingCustomer). If a loading customer exists, then we should show the skeleton.

So now I come down here, and we'll do and And that, my friends, is how you do that. Click, boom, there it is, that sure makes it feel faster. I don't know about you, to me, I feel like that's a faster experience. The one trade-off that we're making, as I mentioned earlier, is if I click on this one, and then decide, I actually wanna go to that, I have to go back.

Wait, actually, I wanna go back, shoot, right? So you are making a tradeoff, it's not just like this is always better. But in my opinion, for this specific situation, I think this is better, I do. So that's what we're gonna go with. And yeah, it's quite nice, considering the user's network is really slow, which is what we're trying to simulate here.

So I think as far as giving the user as good as an experience as possible with their slow network, we're prefetching properly. So I hover over this for a second with my slow network. And then I finally click and that just shows up. You may have noticed there was a flash of loading state right there.

Let's fix that here really quick with our SpinDelay stuff. So let's say, actually our showSkeleton, it's gonna be useSpinDelay, and again, it imports it wrong. I don't know why, but we're gonna fix that, useSpinDelay. Here's my Boolean, and I'll stick with the defaults here too. You can look up that, and kind of move those knobs however you like, but that's what we're gonna go with.

And so now, I'm not sure how, it's hard to simulate this, because it's a random length of time. But we're not gonna get that flash of loadingState anymore. What the devil? I don't know what that was. We're gonna pretend that didn't happen. [LAUGH] I don't wanna take the time to look into that, that's never happened before.

And that is, yeah, that's everything to show you about this. So you do not control the network at all, that is out of your control. What you do control is how your app responds to slower network experiences. And you can do that by prefetching and showing a global spinner.

And then showing more localized loading states like skeletons in appropriate ways. And I think Remix gives you really nice APIs to be able to do that. I feel like this is actually pretty straightforward and simple. If you want to look at how I make this more type safe, you can take a look at the finished version where I do all of that, okay?

Cool, any questions about skeletons? UI skeletons, I don't know anything about human anatomy skeletons. I think my femur is somewhere around here.
>> If you navigate directly to this page, it will take a long time. Is there a way to display the app in chunks?
>> Yes, wow, what a great question.

Holy smokes, it's almost like that's our next exercise. Yes, so you're exactly right, this is not great. If what the problem is, is your user's network connection, you can do nothing about this. I don't care if you SSG or whatever nonsense you're doing. If your user is on a slow connection, you're stuck, this is just gonna take a long time for them to load, that's it.

Same thing with transitioning to this customer's page. You're stuck with that loading spinner, sort of. The next exercise will actually solve that, even for users on a slow network connection. Because even if your backend is really fast at getting you the data, if it takes the user a long time to go and ask for it, and then download it, again, it's still gonna be slow.

There's not much you can do about that, because we are already chunking the application. So even if we don't load data on this page, just to get the code to show the spinners and whatever, that's gonna take some time. And if your user's got a slow network connection, that's it.

So yeah, if you want to, the best you can do is prefetch. And that'll prefetch the JavaScript resources and stuff. And you could also even say, hey, I wanna prefetch the JavaScript stuff right away. They logged in, boom, I just download the entire app. You can totally do that if you want.

But yeah, as far as downloading the data, unless you wanna download all the data to the users device as well, which you probably don't wanna do for most applications, the best you can offer is that spinner. However, if the problem is not your user's network connection, but actually your backend is slow, that's what the next exercise is about.

So great question.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now