Refactor data fetching with useEffect to Suspense Resources

Kent C. Dodds
InstructorKent C. Dodds
Share this video with your friends

Social Share Links

Send Tweet

A very common practice in React applications today is to request data when a component is rendered within a useEffect callback. Let's refactor that to Suspense with our new resource factory.

Instructor: [00:00] Now, we have a little app where we can choose different Pokémon. We could say Mewtwo, and that will show us Mewtwo's information. These little examples are actually buttons that we can click on, because I don't want to have to type that out every single time.

[00:12] This is currently implemented using useEffect, which is basically what Suspense for data fetching is replacing. Let's go ahead and refactor this to Suspense for data fetching.

[00:21] The first thing that we're going to need is we're going to come down here, and we need to wrap this Pokémon info in a Suspense boundary and error boundary to handle when this component suspends and to handle when there's an error.

[00:32] We already have an error boundary. We just need to import it from our utils here. I'll add error boundary. Then we'll come down here and wrap this in our error boundary. Next, we're going to wrap this in a React Suspense component.

[00:51] We'll specify our fallback to be the Pokémon info fallback, and the name for this fallback is the Pokémon name that the user entered. With this, we're now ready for Pokémon info to suspend. Now, we can scroll up here, and you'll notice we have an error state here, but that's now being handled by our error boundary, so we can get rid of that.

[01:15] We have a pending state, and that's now being handled by the Suspense boundary, so we can get rid of that. All that's left is the success state, so we'll just get rid of this if-statement, because by the time our code gets to here, we should have all the Pokémon all loaded.

[01:28] Actually, we're not going to do any of the loading of the Pokémon directly in this component. Instead, we're going to create a resource when the user selects the Pokémon that they want, and we'll pass that resource to this component.

[01:40] We can get rid of the use effect entirely, get rid of our state management for that asynchronous interaction, and instead of accepting a Pokémon name, we'll accept a Pokémon resource. Then we'll get our Pokémon from the Pokémon resource.read method that we're going to call.

[01:58] We need to manage that state in the component that's rendering our component, so we can pass it along. I'm going to add another use state here for Pokémon resource, and we'll have a set Pokémon resource. We'll initialize that to null.

[02:13] Then instead of rendering this based on the Pokémon name, we'll render it based on the Pokémon resource. Instead of passing the Pokémon name, we'll pass the Pokémon resource to Pokémon info, so it can read that information from the Pokémon resource.

[02:26] Now, when the user submits the form, I'm going to call setPokémonResource, and I'm going to call createResource, which I'm going to need to import. Let's go up here. We'll import that here, and then we're going to use this fetchPokémon method again.

[02:43] I'll call fetchPokémon with the new Pokémon name. This will trigger a re-render with a resource that actually hasn't loaded yet. When the app re-renders, we'll come down here. We say, "Oh, we do have a Pokémon resource, so a Pokémon info will get rendered with that Pokémon resource."

[03:01] When we call Pokémon resource.read, this component will suspend, because that resource data isn't ready. When it suspends, React will catch it and find the nearest React Suspense boundary and render the fallback instead while it's waiting for the Pokémon resource to resolve.

[03:15] When the Pokémon resource resolves, it'll trigger a re-render of the Pokémon info component. This time, when the Pokémon info component calls read, because the resource has resolved, it will have the data that it needs to continue to return the React elements to render the Pokémon information.

[03:32] So, if we save this, and everything was done correctly, then we should be able to click on Pikachu, Charizard, and Mew. In review, basically, what we did here is we took all of the logic that was inside of this Pokémon info component, completely removed it, and replaced it with a prop called Pokémon resource, rather than accepting a prop called Pokémon name.

[03:57] We manage that Pokémon resource in the same place where we manage the Pokémon name. As soon as the Pokémon name changes, we know we need to create a new Pokémon resource. We create that resource using the createResource utility that we built earlier, and then we render our Pokémon info based on the presence of that resource.

[04:15] To make the Pokémon info component suspendable, we had to wrap it with an error boundary and a React Suspense component that has a reasonable fallback for the thing that we're waiting to load. Doing this makes our code way more declarative, which results in a code base that's a lot easier to manage.

[04:30] One last thing I'm going to do here really quick is I'm going to take this createResource call, and I'm going to make a function called createPokémonResource. We're going to just do a little bit of composing here to create a resource specific for a Pokémon.

[04:45] This, I'll pass the new Pokémon name, and then I'll make a function called createPokémonResource. It'll take the Pokémon name, and then it will simply return the same stuff that we just cut. We can clean that up a little bit here.