/src/exercises/05.md
https://github.com/kentcdodds/concurrent-react · Markdown · 114 lines · 88 code · 26 blank · 0 comment · 0 complexity · 9aa0dd1de4658daeab0fbda086c54500 MD5 · raw file
- # Suspense Image
- ## Background
- Loading images is tricky business because you're handing the asynchronous state
- over to the browser. It manages the loading, error, and success states for you.
- But what if you have an experience that doesn't look any good until the image is
- actually loaded? Or what if you want to render a fallback in the image's place
- while it's loading (you want to provide your own loading UI)? In that case,
- you're kinda out of luck, because the browser gives us no such API.
- Suspense can help us with this too! Luckily for us, we can pre-load images into
- the browser's cache using the following code:
- ```javascript
- function preloadImage(src) {
- return new Promise(resolve => {
- const img = document.createElement('img')
- img.src = src
- img.onload = () => resolve(src)
- })
- }
- ```
- That function will resolve to the source you gave it as soon as the image has
- loaded. Once that promise resolves, you know that the browser has it in it's
- cache and any `<img />` elements you render with the `src` set to that `src`
- value will get instantly rendered with the image straight from the browser
- cache.
- ## Exercise
- If you turn up the throttle on your network tab (to "Slow 3G" for example) and
- select pokemon, you may notice that images take a moment to load in.
- For the first one, there's nothing there and then it bumps the content down when
- it loads. This can be "fixed" by setting a fixed height for the images. But
- let's assume that you can't be sure what that height is.
- If you select another pokemon, then that pokemon's data pops in, but the old
- pokemon's image remains in place until the new one's image finishes loading.
- With suspense, we have an opportunity to make this experience a lot better. We
- have two related options:
- 1. Make an `Img` component that suspends until the browser has actually loaded
- the image.
- 2. Make a request for the image alongside the pokemon data.
- Option 1 means that nothing will render until both the data and the image are
- ready.
- Option 2 is even better because it loads the data and image at the same time. It
- works because all the images are available via the same information we use to
- get the pokemon data.
- We're going to do both of these approaches for this exercise (option 2 is extra
- credit).
- ## Extra Credit
- ### 💯 avoid waterfall
- If you open up the network tab, you'll notice that you have to load the data
- before you can load the image because the data is where we get the image URL.
- You may also notice that the image URL is always very predictable. In fact, I
- even wrote a function for you to get the image URL based on the pokemon name!
- It's exported by `src/fetch-pokemon.js` and is called `getImageUrlForPokemon`.
- ```javascript
- const imageUrl = getImageUrlForPokemon('pikachu')
- ```
- Try to pre-load this at the same time as the rest of your data. This one will be
- a bit trickier. I'll give you a hint. There are several ways you could do this,
- but in my solution, I end up changing the `PokemonInfo` component to this:
- ```javascript
- function PokemonInfo({pokemonResource}) {
- const pokemon = pokemonResource.data.read()
- return (
- <div>
- <div className="pokemon-info__img-wrapper">
- <img src={pokemonResource.image.read()} alt={pokemon.name} />
- </div>
- <PokemonDataView pokemon={pokemon} />
- </div>
- )
- }
- ```
- ### 💯 Render as you Fetch
- Remember when we did this with 02? Now that we're pre-loading the image along
- with the data, the improvements will be even more pronounced. Go ahead and put
- this at the top of your file:
- ```javascript
- import createPokemonInfoResource from '../lazy/pokemon-info-render-as-you-fetch-04.data'
- const PokemonInfo = React.lazy(() =>
- import('../lazy/pokemon-info-render-as-you-fetch-04'),
- )
- ```
- And make that work. Then checkout the network tab and see how your waterfall has
- turned into a... stone wall? Yeah!
- Again this is a good one to have `window.fetch.restoreOriginalFetch()` called so
- you can see a real network request for the data. You will notice that we're
- making two requests to the pokemon endpoint. The first is an OPTIONS request.
- That's basically a request the browser makes to ask the server if it will accept
- our request across domains. It's kind of annoying because it makes our network
- requests take longer, but it is what it is.