Watch on YouTubeThe player loads when you press play.

In this video

To size an image in the drawing tool from the performance tutorial, we first need to load its dimensions.

I use an asynchronous selector to fetch the dimensions, then apply them to the element. A Suspense boundary around the part that needs those dimensions lets it show a fallback while it waits. The rest of the editor can carry on without a loading flag for the whole app. In part two I use the same approach with a real API.

Follow along with the project branch.

Read the automatic transcript

YouTube generated these captions and sometimes gets names, punctuation, and code wrong. Check the recording if something reads oddly.

0:00 — hey folks today we're going to build a design tool using recoil and react suspense to deliver a next level user experience let's dive in so we're going to be building on top of our existing recoil design tool that we built in our previous video so if you haven't checked that out yet i would highly encourage you watch that first before diving into this one so let's start by having a look at what we already have so we have this design tool and you can insert a rectangle you can drag it around and you can see the properties change on the right

0:30 — as you drag it around or resize it i can also change the properties over here on the right and change the color so i can also insert an image but you can see the image is a bit squashed and that's because when we insert the image it just gets inserted at this fixed 200 by 170 size so the first thing we want to do is when we insert an image we want to display a spinner and then have the element automatically resize to the correct size for that photo the link to the github repo is in the description so that you can clone it and follow along so let's head into the image component

1:01 — which is where we actually display the image and here you can see that we get the element and if the element isn't an image then we just return null so we don't display an image at all but if it is an image then we display this div which has a background image and that is how we actually display the image and this is the url for the image so let's import a function from utils and this function is called get image dimensions so we can pass our url for the image into this function and it's going to give us back the width and height but this function is asynchronous right

1:32 — because it needs to fetch the image asynchronously and then we can get the width and height and this is where recoil is going to come in so let's import selector family from recoil and we're going to create a selector here which we're going to call image dimensions state and we're going to say that it's a selector family and we'll say that the key is going to be image dimensions so let's pass the get function but because this is a selective family and not just a normal selector we actually have a function that returns a get function and this id here will be

2:03 — the id of our specific element so here we can get the get function and what we want to do here is get the element state for our particular element and we just need to tell typescript here that the id is going to be a number so now let's just log our element state over here to make sure it works and now we can call use recoil value and pass in our image dimension state and pass in the id of our element and if we head back to our design tool insert an image you can see that our selector runs and it logs out

2:33 — our element state for us this is great because this means that we can now get the image url from here and return our image dimensions so let's remove the console log so the first thing that we want to do is we want to say that if the element type is not an image then we're just going to return null but if we are dealing with an image we're going to return the result of get image dimensions and we're going to pass the source of the image into get image dimensions so you can see here that we're actually returning a promise from our get function meaning that this

3:03 — selector is now going to be an async selector so currently we're not actually getting a value from our selectors we're going to say image dimensions is equal to our user recoil value and just to neaten this up a little bit let's just reorder these hooks over here so now let's log our image dimensions and see what we get so if i insert an image oh something bad happened and we can see here that what happened was it says that image suspended while rendering but no fallback ui was specified and that's because this user equal value

3:34 — because it's returning a promise is causing our image component to suspend before we go any further i just wanted to say that if you found this video useful please give it a like and subscribe if you want to see more videos to help you level up your coding anyway let's get back to it when a react component suspends that's basically that component saying that it isn't ready yet but when we're dealing with react components that are suspending we need to add suspense components into the react tree so that we can render fallbacks for when these components aren't ready yet

4:04 — so the first thing we want to do is head to our app.tsx and add a global suspense component so let's import suspense from react and we're going to wrap our entire app in a suspense component and we can pass a fallback to our suspense component which is going to render when anything inside of this suspends component is suspending and we have this centered loading component which we're going to import from our ui folder and this scented loading component is just going to render a spinner in the middle of the screen so i insert the image and you can see

4:35 — that the entire app disappears and a spinner appears in the middle but that's not an ideal experience because the rest of the app isn't suspending it's only this image component that's suspending so although the suspense component at the top level is great because it catches any components that don't have suspense wrapped around it let's be a bit more fine-grained about where we render our suspense fallbacks so let's head into the element component and here you can see we have our element container which renders out our image and this

5:06 — image is the component that suspends right so if we wrap just this image component in a suspense component and let's say that the fallback here is going to be element fallback which we can import now if we insert an image you can see that the spinner only displays inside this element and that's because our image suspends and then the suspense propagates up to this suspense component rendering this element fallback spinner over here and that's a great experience because i can insert a bunch of images

5:36 — and their loading states will all be independent so if we look at our image component we can see that we've logged image dimensions over here and if i open up the console you can see that image dimensions gets logged out and we get our image dimensions for each image that's great you can see that when we drag this image around it suspends again and we don't want that and why is that happening well it's happening because whenever we drag the image around this selector is re-running and it's re-running because the element state changes and that's to be expected

6:06 — because the idea behind a selector is that anything inside of that selector that you get if it changes that selector is going to rerun but all we're actually interested in in this selector is the element source right we're not interested in the top left width or height but because we're getting the entire element state the select is going to rerun because this element state is changing right so to have some more fine-grained control over when the selector runs we can add another selector so let's create another selector

6:36 — and we're going to call it image source state and that's going to be a selector family the key is going to be image source and we're going to have our get function again and what we're going to do is we're going to copy our logic here where we get the entire element state and put it in our get function here but all we're going to do is just return the element source so now this selector family will give us back a string instead of giving us back the entire element state so now we can take this selector and instead of getting the entire element here we can just get our selector and this is

7:08 — going to give us back not the whole element but just the image source so you can see here that the result of the selector is either going to be a string if it's an image or null if it's not an image right so now we can change our check over here to say if we don't get a source back then we just return all again but if we do get a source back then we just pass the source in target image dimensions and now what's going to happen is when the element changes we're going to get the element source in this selector and only when this element source changes

7:40 — will this selector rerun so let's take a look i insert the image it suspends i get a loading spinner once the image loads and i drag it around you can see that it no longer suspends when i'm changing any of the other properties because the source stays the same and to clean things up a little bit more we can even use this image source selector to get the image sourced down here so let's change our element user equal value over here to say image source is equal to use recoil value but

8:10 — we're going to pass in our image source state instead and that's of course going to give us back just our source and again we can say if there's no image source then we return null and then we just pass our image source straight in so what we want to do is when we get these image dimensions back we want to set this element's width and height to the width and height that we get back from our selector and to do this we're going to add a use effect so we want this use effect to run whenever the width or height that we get back from our image dimensions changes so i'm going to extract width

8:42 — and height into their own variables so that we can add them as dependencies to our use effect dependency array so now if we log our width and height inside of our use effect and we head back to our app and insert an image you can see that when we get the dimensions back we get the width and height so next let's import use set recoil state from recoil and we're going to create a new variable here called set element and we're going to call use set recoil state and we're going to pass in our element state and pass in the id of

9:13 — our specific element to get that atom and now when we get the width and height we're going to set the element so we can pass a function into our set element that's going to give us the current value of our element and we're going to return the current value of our element and the current value of our element style but with the new width and the new height added in and just to keep typescript happy we just need to return early if there's no width or if there's no height and let's add in the set element function to our use

9:43 — effect dependency array so now whenever width or height changes from our image dimensions we're going to update the element with the new width and height so let's see this in action so i insert the image and you can see it's 200 by 170 and then once it loads in the width and height gets updated and this is how we can use a combination of recoil selectors that can return a promise and react suspense and react use effect to build a reliable and straightforward way of fetching data and adding that data into our local

10:14 — state and we can clean this up a little bit as well so what i'm going to do is create a new hook called use set default dimensions and i'm going to take all of the logic that we wrote over here and move it into our hook and all we need to pass into our hook is an id which will be a number and now we can simply call this hook in our image component and all of our logic is extracted and reusable in the next video we'll be expanding our functionality a little bit more and we'll actually be fetching some data from an api using recoil

10:45 — so that's it for this video be sure to check out the next one and be sure to like this video if you found it useful and subscribe if you want to see more content that will help you level up your coding you

Originally published on Jacques Blom.