It's important to remember that everything we've made can be created in React using their standard library. This lesson highlights the differences between the patterns we're following and the standard React way to show how beneficial patterns can be to take care of complex nested logic.
John Lindquist: [0:00] Let's re-implement this behavior in a more standard variation of React code. I'm going to comment this all out and build it using the standard hooks. I need an onInput. We'll do that with a useState. We'll grab the inputValue and a setInputValue.
[0:17] While useState, set that to an empty string. In here, we can tell the event to go into the setInputValue and pass in an event target.value. That's what we did in this line right here. Once this inputValue changes, we'll start doing the bouncing, and filtering, and mapping logic.
[0:43] Anything asynchronous like that, means we have to use the useEffect hook and this takes a function, and this function is called when a dependency changes.
[0:52] I'm going to say inputValue. This function is run when this value changes which is conceptually similar to these functions being called when this value changes.
[1:03] From there on in, we can set a timeout, put a function inside of there. We wanted this at 500 milliseconds. Then the value that gets set inside of here is our books. We need another useState. We'll say books and setBooks. We'll start this with an empty array.
[1:23] I'll set books inside of here with some value. Just to start off with, we'll do a title of "Test" to see if everything is working right now. Looks like I need to import everything. Import useState and import useEffect. Hit Save. Now it looks like this is running on the first render, and we don't want that to happen.
[1:47] Let's set up a firstRef. We'll use ref on this. We'll start this at true. Then in our useEffect, we can say if firstRef.current, then update firstRef.current to false, and then stop it from running. Hit Save here. Now that shouldn't appear until I type a character. You see Test appeared after 500 milliseconds.
[2:16] Now let's set up a canceling for our timeout. We'll need a timeoutRef.current is set to that. Make sure and set up this ref. We'll say let timeoutRef = useRef. Then we can check if timeoutRef.current, then clear timeout, and pass in the timeoutRef.current. Now I should be able to type something quickly, and Test won't show up until I'm done typing.
[2:45] We'll type STAR or let me type in a whole bunch of more characters. You'll see nothing will show up until I stop. That's working. Then inside of our setTimeout, we're going to want to fetch. I'll use async/await on this. We'll fetch the books API. Let me grab this. I'll copy that, put that down here. It looks like we named that name. I'm going to change inputIValue to name. That's an easy refactor there.
[3:16] We can await the result of this. Hit the response and we can pull the JSON off of that. We'll await response.json and then we can set our books with the json.docs. Let's save here. It looks like I forgot the rename inputIValue somewhere. This one right here. We'll type in name. Now, when I type, it should make a request on the first letter and you see all the books showed up.
[3:46] Because we want to wait until we type at least four characters to search, we'll do a if name.length > 3, wrap that around this. We can also do the other scenario, if name.length < 3, then we can set the books to an empty array.
[4:07] Let's try this out. I'll type in STAR, you see all the books. I'll delete two characters and it clears out the books. Now, let's set up cancellation for our fetch. We need to create an AbortController.
[4:21] We'll do a controllerRef, and use the ref, then we can set up our controllerRef.current to be a new AbortController. Before we even do that, we'll check to see if the controllerRef.current exists, then we want to controllerRef.current invoke the abort.
[4:51] Then we can get the signal off of the controllerRef.current.signal. We'll pass that in as a parameter in our fetch call. If there's any outstanding requests as I start typing, so we'll type STAR then S, you'll see it aborted the request in checking the network that we successfully aborted.
[5:12] You'll see it right there. Then we can try this and catch the error. We don't need to do anything with the error right now. It just means that you aborted an outgoing request. Now if I type STAR and then S, you'll see this aborted, and everything worked great.
[5:35] We've successfully re-implemented all the features we had before. If you look at this block of code, you'll see that there's a lot of moving pieces. A lot of it is simple cleanup with controllerRefs and timeoutRefs. In fact, thinking about this, useEffect should return a function that tears things down. I'm going to bring this down here as well.
[6:04] That's similar to the pattern we've been using, as far as returning a function to clear things up. This is what we've ended up with doing asynchronous code in standard React syntax. To be completely fair, you could extract these to your own custom hooks. I'm sure this could be greatly improved.
[6:22] The two main points I wanted to call out, though, are that there are a lot of moving pieces. There's stuff that changes, updates a callback, and then outputs something. The fact that this changes and pushes a value into here, and then at the end, there's a listener that is set with data., there's that callback smell in there, even though this pattern is completely different.
[6:46] The main point I want to make is if I want to change it so that my debounce logic lives after the filter logic. Even after the filter logic in here, you'll see that these are nested inside of each other. Refactoring with asynchronous code gets pretty tricky pretty quickly.
[7:04] When you extract all these behaviors like this into these functions, if I want to refactor this debounce in this filter, so that this debounce ran after this filter, I would simply move this line down. Then everything is taken care of for me. If I want to do that in here, I would have to move this line down inside of this, then I think move this line up inside of this one.
[7:37] Your confidence quickly drops when things are nested like this, that you know what you're doing. You've changed that this is no longer happening after 500 milliseconds. Another small, minor thing you may have missed is that the timeoutRef is not being cleared in the same spot anymore. That'd be really important if this was something asynchronous.
[7:59] There's just a ton of moving pieces in here. Whether you use the patterns I've showed so far or you come up with your own, I strongly recommend doing something that manages this asynchronous logic for you.