Advanced Remix

Non-Nav Fetching Solution

Kent C. Dodds

Kent C. Dodds

Professional Trainer
Advanced Remix

Check out a free preview of the full Advanced Remix course

The "Non-Nav Fetching 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 walks through the solution to the Non-Nav Fetching exercise.

Preview
Close

Transcript from the "Non-Nav Fetching Solution" Lesson

[00:00:00]
>> Welcome back, hopefully you had a great time with exercise five, we're gonna work through it together and feel free to ask questions and things. I'm gonna start on the new TSX file, so this is our customer combo box. ComboBox, that is exported from our resource route. So, if you look at that import that is coming from a routes resources customers.

[00:00:24]
I'm gonna leave it like this full import rather than doing relative import, I just prefer having that till date. I don't want to do a relative import there. Because yeah, like who wants to do that? Not me, no. So, we're gonna go with that. And again, you do not typically want to see this in your codebase, you should not be importing things from routes most of the time.

[00:00:49]
This is an exception, and it's a darn good one. So, the other thing that this customer combo box accepts is the error because it's the one responsible for rendering our inputs and so it's gonna render the error force as well. And we get our errors from the action data similar to this.

[00:01:06]
And that's gonna be errors dot customer. This will get passed on in our customer combo box. Okay, great, so, in the customer combo box, I actually created this pattern when I created this workshop, I was like, okay, I've got to have a backend connected component where I'm gonna put that component.

[00:01:29]
Well, here's my resource route. I guess I could put make a components directory or something that why don't I just stick it in there and then I can export it and it actually is so nice to be able to have this back end front end. So, this is all very much a front end thing.

[00:01:42]
We've got Downshift to this use combo box hook that we're using and rendering out all of our UI stuff for that. I think it's pretty rad. And then we have our back end code right here. I think we wanna start is with our UI. But you could really start with whichever one that you want.

[00:02:07]
But I'm gonna start with the UI. And so, we'll say we need to use fetcher that's gonna be triggering the fetch to our research route. So our fetcher, use fetcher. And fetcher seems like a pretty reasonable name for me. So, I'm gonna stick with that. The fetcher when we do fetch is gonna have a data property.

[00:02:27]
So, when that fetch is successful, it's gonna have a data property that we can use for the customers that we feed into our combo box, the customers or the items. And so, what we're going to do is say fetcher.data. And I think that the way that I do in the final, is we say dot customers.

[00:02:49]
Because for me, I typically like objects coming back with descriptive key names, but you could just return the array, that is fine. So, if the fetcher data customers exist, then that is what it is gonna be. Otherwise we will have it be an empty array. Okay, so, if we have already fetched and we have got some customers that is what we want to show on our list.

[00:03:11]
Otherwise, we don't have anything in the array there. Great, so then, in our use combo box on input value change, we need to do a few things to actually trigger this fetcher to submit this query. So, first we're going to say, if the changes that happened.inputvalue. So, if there's no input value then we'll just return or just quick exit, we don't need to make any fetch request in that case.

[00:03:46]
We could potentially store some state here and say set customers to an empty array to clear it out that's an option that you have if you wanted to do it that way. But we're just gonna leave the customers that were there at the time that the input value got grayed out.

[00:04:02]
So, that's a UX choice that you can make there. Okay, so, if there is an input value, and we had that change, then we're going to submit. So, we're gonna say fetcher.submit. And we're going to submit our query, which will be changes.inputvalue. So, this this query is our search.

[00:04:25]
And this is actually gonna go on the search params on the query params cuz we're actually going to set this to a method of get. So, we're going to submit this as a get. And if we do not provide an action here, then the action will default to whatever route this component is rendered in.

[00:04:47]
So, it could go into, in our case that will render in here and we would have to have a loader that handles this request in there and that just does not really make sense. I don't wanna have a loader that handles the back end for this component in every route that I render this in, that's what we're trying to do in this exercise is avoid that.

[00:05:06]
And so, we're going to specify an action and that will point to the route that we're currently in which is resources customers. So, we'll say resources/customers. And this is where things just get so magical because now I'm looking at the back end code for my front end code.

[00:05:27]
Goodbye indirection. That sounds like a band one direction, but it's no indirection. So, this is great. So, that's what we need to do to make sure this ends up in the loader is to set it to again and when you set it to get this will be serialized as part of the query string of the request.

[00:05:46]
So, first thing in our loader we wanna do is to require the user we don't want just random people to curl this URL and get all of our customers cuz that would be bad if they found out that company x is our customer. So, first we'll say, await require a user and we'll pass the request we're gonna get here.

[00:06:07]
Let's say loaderArgs, and we'll pass along the request. So, make sure that we've got an authenticated user before making this request. And now, we're going to perform the search with search customers. Now, where do we get this query that was passed along? We're gonna get that from the search params on the request.

[00:06:30]
So, let's turn this request URL into a URL. So, a new URL, and that's platform API right there. request dot URL, also platform API. So, now we've got the URL, we can get the search params off of that. So, we'll say search brands is url.searchParams. Here actually, let's just call this our query.getquery.

[00:06:56]
So, now you've got a query and this one is interesting. So, this kind of resembles the API for form data, right, where you say dot get but this can only be a string because you can't put a file in the query string of a URL. So, it can only be a string or it could be null.

[00:07:13]
To take care of Typescript and just like check ourselves because it should never be no because we're never calling this without a query. But maybe somebody is gonna do something silly so let's just put an invariant in here, it says type of query string, and we could say query must be provided.

[00:07:34]
Alright, so, with that now we can say search customers with that query. And here's the matching customers or we'll just call it customers and that's gonna be a wait cuz it is an a sync function. And now, the last thing is return a JSON response with the customers and that JSON response is gonna come from the same place our loader actually came from, Remix run node.

[00:08:06]
All right, great, I think we should be set snap there was a problem. Well, let's figure out what the problem was then. So much stuff, cannot read property reading customers. All right, that is my bad data could be undefined. There we go. So, now, we say, yeah, wide open spaces.

[00:08:31]
We select that one, we say here's my due date, here's the quantity, we had two add to 5000, it's Koala paintings. Create invoice [SOUND] and remember none of my code is saying we just created a new invoice, so let's add that to our array of invoices. None of that because we were revalidating for you automatically just like the way the web does, so that's great.

[00:08:58]
And that is creating an invoice, we've just created a component that is connected to its back end. Yeah, I think that's pretty sweet. What questions do you have about what we have done here? There are things that we could do, I should have mentioned, or things we could do to make this more type safe add a bunch of TypeScript stuff.

[00:09:20]
This is not a TypeScript workshop, so I'm not gonna bother with all that. But yeah, just know that's actually why I ran into that runtime errors because I haven't made this type safe yet. So, Peter, yeah.
>> In the loader, you can use the request but there's also a params key, could you use that to get the query params?

[00:09:38]
>> So, I'm glad that you asked that. So, these are search params and these are request params. The request params that is a Remix specific thing or react router as well. And that would apply if I said dollar customer ID, and then I could get the customer ID off of that.

[00:09:56]
So, that's what those params are referring to the parameters, the search params are like actually here we can witness this with our very own eyes if I type something then we're gonna get customers. And you'll see there's our search params right there that we added. Remix is adding another query or search params there, but it strips that off before it hands it off to you.

[00:10:18]
That's all implementation detail stuff. But you can see your search params right there. And yeah, thanks for the question. Any other questions? Yeah,
>> What benefits do you get by putting the fetcher inside routes in the first place? If in general it's a bad idea, why can't it be just a separate component?

[00:10:40]
Is it because it needs this fake route for its back end loader?
>> Great question. So, to be clear, this is not a fake route at all. This is an actual route and we could take this out. And you can hit this API as like an API route specifically.

[00:10:58]
So, we call this a resource route. A resource route, is a route that does not export a default function. So, no default export. Now you can hit this like it's an API endpoint. So, there's nothing fake about this loader, what we're doing the benefit is if I want to have a component that tucks sorry, that talks specifically to this loader.

[00:11:21]
Then typically what I would do is I'd put it in at components directory or something and stick it in there and then I'd have indirection between the component that cares about the route and the route that handles it. Because they're in different places, but by putting it here, I is still technically have indirection, right?

[00:11:42]
Because if I change the name of this file, then yeah, I've totally busted this. But, I mean, you maybe could do some code gen to like autofill. That would be kind of interesting, but that's not something that was built into Remix. But the indirection is greatly reduced because you see exactly the component that is responsible for talking to this back end, which just may that's so nice.

[00:12:07]
So, I do definitely recommend this pattern, and that is why I'm teaching it to you today. And just to be clear, we could not get the same behavior if we moved all of the code in this file to something outside of the routes directory. For this loader to be a loader, it has to be in the routes directory.

[00:12:31]
>> Would you add debouncing in this scenario?
>> Great question. Yeah, so, it is pretty common practice to debounce the input so that you don't search instantly as the user is typing. And you can see maybe why as I type in here, every single time I type something I'm making another request.

[00:12:53]
And normally, I have done that, I have added debounce. In this case, I actually think it's fine. Is there a problem with this? I don't think so. Unless your backend can't handle a couple of requests from a couple of users. I don't know. It seems fine, there could be some infrastructure related limitations that you've got but for me this actually seems okay.

[00:13:20]
I think the reason that I would typically debounce is because I wasn't confident in my ability of handling race conditions, but Remix handles those of handling race conditions. But remix handles those for me automatically so, I don't have to think about that. So yeah, I probably, if I were building this UI today, I would not bother adding a debounce, if you wanted to add a debounce then you absolutely could, you have your debounce function.

[00:13:44]
Debounce inside of React code is kind of surprisingly tricky, but there are probably NPM packages for it. You'd have your debounce function out here that's responsible for doing the fetcher, and then you'd call it in here. And that should work. I think actually you may be able to create the debounce function, now you'd have to put it inside the component so you have access to the fetcher which is a shame, made me work but it's Debounce always complicates things.

[00:14:13]
It's not gonna be a lot but enough that I don't see enough of a problem to add it. And on top of that debounce also makes your app feel slower. Because now you're automatically having to wait two to 300 milliseconds before any requests go out. So, this is this pretty fast.

[00:14:31]
The other thing is we haven't done anything here with this yet but you can add headers and cache control, for a second I forgot what this header is called. And max age of 10 or 30. And so, as the user is typing I go backspace and they're making the same request they're gonna get the same data they did before and that's all just stored in the browser.

[00:15:01]
So, it's user specific and everything. So, in that case you're hitting your back end less even though you're making all of those requests. So, that might be something worth adding, I would certainly add that before I started thinking about debounce. Yeah.
>> How would you approach this progressive enhancement without JavaScript?

[00:15:21]
>> Yeah, okay, good question. How do you do this with progressive enhancement? So yes, there is no way that this progressively enhances by itself. Because when you're doing imperative data fetching, imperative by itself just says cannot be done without JavaScript, you got to have clients that JavaScript. So, if the progressive enhancement story matters a lot to you and your app then what you could do is initially render this as a select.

[00:15:52]
Or there actually is a built in I think it's called a dataset input element. And you could have it be a dataset. The problem with that is if you have a really big customer list, which is typically why you build something like this search in the first place.

[00:16:08]
Yeah, that's gonna be loading too much data, right? So, in that case, if you really needed the progressive, I would not bother with this. Let me just state that clearly, I would just say, app only works with JavaScript for this particular use case. But if it was important to you, then you would instead allow people to input the customer ID, because that's what's ultimately gonna be submitted.

[00:16:35]
In fact, when I select wide open spaces, if we dive into this, we'll see the input right here, the value is wide open spaces but I also render an input right here with the name customer ID. So, this is actually that workaround I was talking about and the reason downshift is accessible and it does the web thing.

[00:16:56]
The problem is that I don't wanna submit the customer name, but I wanna display the customer name. So, that's why I'm doing this little workaround where I can have a hidden input for the customer ID. So, you could just require that they provide the customer ID and then you have another page that has all the customers and they look it up over there or something.

[00:17:15]
Okay, so yeah, it's challenging with some more of these advanced API's but totally possible.
>> Can I get extra points for using fetcher dot load? Or maybe I did something wrong.
>> Yeah, that's a good question. You can totally use fetcher dot load and in that case you don't get the second, how does, I don't use fetcher dot load very much.

[00:17:39]
I think it expects the URL here, let's look at what this says. Yeah, it expects the h ref and then you construct the search string, that works totally fine too. I just find this to be a little easier. But yeah, here if we wanted to do it this way, we'd say url search params, new url search params or you could construct it yourself but then you need to make sure you escape it and all that.

[00:18:04]
So, I'm not gonna bother with that nonsense. We're gonna say, put our query thing in here. And we'll say resources customers and then search params. So, that is functionally equivalent to what we've done. Extra points, I don't know, are I don't just give those out, yeah, totally extra points, but that should work.

[00:18:33]
I would just use the submit. I just think it's easier.

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