Proper Suspense code can mean a lot of functions wrapped in other functions.
Because these function are composed with Hooks, modules can't help us hide the implementation details.
But Context can!
Let's explore how Context Providers, Consumers, and the useContext
Hook can integrate with Suspense to mask wordy useTransition
-wrapped API calls.
Instructor: [0:00] Let's explore and create a Context.Provider for our resource reading components. For me, context always starts in a new file that I can import wherever I need it.
[0:12] Here, I'll start a new file called pokemon.js. As this application grows, I'll put a lot of named exports in here, but we'll start with context. Import React from 'react'. We'll need that for creating context, and export const PokemonContext, which we'll create with React.createContext.
[0:39] Back to our app, where we will use it for the first time. Let's import it at the top. Now, it's a named export, so we need to use these curly braces, PokemonContext from Pokemon.
[0:56] In this, Pokemon context is both a provider and a consumer. Let's use the provider to wrap this Pokemon detail component, which we already know is consuming a Pokemon resource. PokemonContext.provider, we'll give it as a value an object that we haven't created yet, PokemonState. Now, wrap our PokemonDetail.
[1:21] The goal is to update PokemonDetail in such a way that it doesn't require these props. Let's move up a few lines and create our PokemonState. What PokemonState = new object, change this to Pokemon, update the syntax but leave the assignment the same at the bottom for isStale.
[1:49] Now we have this little object that we're using to provide as context to PokemonDetail. Next, we just need to update PokemonDetail. At the top, we import our context again as a named export, Pokemon context from ./Pokemon. We can delete these props. We won't need them anymore.
[2:14] We'll use React.useContext, the hook to capture context from Pokemon context. We will assign that to a couple of local variables. We'll reassign Pokemon to resource since we already have that here and take isStale as it is.
[2:36] Our app's complaining about this Pokemon value. Let's refresh it to make sure that's valid. Now it looks like our context is invalid, so let's go back and see what we're doing wrong. Looks like I just need to save this app file. There you have it, PokemonDetail is now using context to get its value. I'd like to set values as well. What does that look like? How can we set a Pokemon?
[3:05] Let's a find a function where we're doing it already. Here in PokemonCollection for renderItem, we are setting Pokemon anytime one of these buttons is clicked. Let's comment this out so that we can have access to it later and get all the context in place. We'll call a function called setPokemon, and we'll use the same value as before, PokemonID.
[3:31] We need to wrap this in a PokemonContext.Consumer if we want to get the callback out of it. It uses a render prop API that we can pass a function to. That function will have access to the state that we gave it, including the setPokemon function, which is all we need here.
[3:56] Take that and wrap it on over to the bottom, and we're good to go on this side. We just need to move this function up into our PokemonState object, and format.
[4:08] We have one last problem. Our PokemonContext.Provider we initially kept very tight, but we're going to have to move it up in the tree if we wanted to include everything that we have, all the way down to this setPokemon down here.
[4:25] Let's keep our fingers crossed, see how we did. "Pokemon is undefined on line 33." Of course, we need to take an ID and use that instead. Format, save, and click a few buttons. We did it.
[4:46] Something that I like about this is that we can create a setPokemon function that has everything that we need to make these transitions nice and smooth. Additionally, components that read resources, like this Pokemon.Detail component, aren't truly agnostic. You can't just take any Pokemon and then render it. They are always going to read from a resource.
[5:10] If they're already coupled to the application implementation, why not also read them from context? Of course, an issue that we have here is that we're recreating this state object every render. If it gets really big and unruly, maybe you might see some performance issues.
[5:27] As for right now, with the application that we have, I really like it. It hides all of the Suspensey details and lets us call functions like setPokemon instead of startTransition arrow function setPokemonResource Suspensify fetchPokemon with ID. Your mileage may vary, but as far as working goes, it definitely works.