useTransition() vs useDeferredValue | React 18

In Web Development


useTransition() vs useDeferredValue | React 18 - read the full article about React update, Web Development and from Academind on Qualified.One
alt
Academind
Youtube Blogger
alt

React version 18 introduced a bunch of new under-the-hood features.

And I do have a complete Whats New video, which I do recommend that you check out before taking a look at this video.

Because this video will be about one specific feature introduced by React 18, Concurrency.

Arguably, the most important feature though.

Now as I mentioned in the other video, generally, your React code doesnt have to change just because you updated.

The only thing you will need to change is an index.js where you have to create such a root object and called render on root, and I already did it here.

And you find this starting project attached to this video, I link into it, attached to this video so that you can follow along if you want to.

Now as mentioned, in this video, were going to take a look at the Concurrency feature and specifically at the useTransition Hook and the useDeferredValue Hook.

These two Hooks.

And for this, I prepared a very simple app here, a very simple React App.

Where in my App component, Im generating a bunch of dummyProducts.

Im doing this here in data.js, just looping through 10,000 items and generating 10,000 Product therefore, though a product is really just a string here in this demo.

And then this product array is used in App.js to in the end output it here in the ProductList.

I also have a filter input field though.

And that filter input field in the end, as you can probably imagine, just filters the products by storing the entered term as a filterTerm and then by recalculating the filterProducts with help of the filterProducts function here, where I use my dummyProducts to filter them based on the filterTerm that was provided.

Thats how this basic app works.

Now thats the user interface we get.

And here, we can type 12 to find all the products that have a 12 somehow in their name or a 122, and so on.

And Im on a MacBook Pro here, so therefore everything is pretty smooth and fast here as I type.

And it updates pretty much instantly even though thats a super long list of 10,000 items as you saw.

Now problems begin if I do throttle the performance here though, which I can do with the dev tools.

There in the Chrome dev tools in the Performance tab, we can turn on CPU throttling, and if we slow down or if we simulate a CPU thats six times slower, then you will see that as I type here, things get a bit more clunky.

You will see that the overall user interface looks and feels a bit laggy, especially the input field feels unresponsive because as were typing and especially as were deleting characters, it lags behind.

You, of course, dont see when Im hitting that Delete key on my keyboard, but I can tell you that Im hitting it and then I have to wait for the input field and the list to update.

And this of course is not great.

Laggy user inputs.

So laggy input fields, which dont instantly reflect what we entered.

Typically are not a great user experience.

And thats therefore a problem.

Now even before React 18 a good solution for this problem would probably have been do not work on 10,000 items at a time.

Maybe use pagination or any other technique or do the filtering on the server-side instead of the client side.

And these are all possible solutions you should take into account when encountering problems like this.

However, if you must perform this kind of operation on the client side, so in your client side code, then with React 18, you now got some tools that can give the user a better perceived performance by delaying some state updating operations, by telling React that some updating operations have a higher priority than others.

Thats the idea behind the Concurrency feature introduced by React 18 and the Hooks and functions that are related to it.

Now for that, back here in App.js, lets see how we can use these new Hooks.

And Ill start with the useTransition Hook, which imported from React.

Now we also have the startTransition function.

Its closely related to this Hook but its meant to be used in places where the Hook cant be used.

For example, class-based components.

Here, I have a functional component though.

Therefore, we can use this useTransition Hook and simply executed here in this App component.

And the useTransition Hook then returns an array with exactly two elements, like useState, which also returns an array with two elements.

Though, the two elements we get here are totally different and unrelated to useState.

What we get here is a isPending value, its a Boolean, true or false, and a startTransition function.

Thats the function.

We could also import separately if we couldnt use the Hook, but if we can use the Hook, we should use the Hook because then we also get this extra isPending value.

Which simply tells us if some state update with a lower priority is still pending execution, which you could use to show some indicator to the user.

Now how do we use this useTransition Hook and the startTransition functional? Well, the idea is that you can wrap state updates with startTransition if they should be treated with a lower priority.

And we could do this here in updateFilterHandler.

There, I can call startTransition and then you pass an arrow function or a function in general to startTransition.

And inside of that function, you perform your logic especially your state updating logic that should be treated with a lower priority.

So in my case, Im setting the filterTerm with a lower priority.

And now if we save this with startTransition added here and we go back, you will notice that as I type the user input field, will be more responsive than before.

Still may be a bit laggy, but way better than before, especially as I delete characters.

Again, you dont exactly see when Im hitting that Delete key on the keyboard but I can tell you its way faster, way more responsive and you can simply test this on your own device.

Now one key difference you will notice if you watch closely, is that the list is now not synced with the user input.

The user input might already be empty, whilst were still seeing some filtered results in the list.

So the list lags behind and this is exactly what startTransition does.

We told React that updating the filterTerm has a lower priority than other state updates and the updating of the input field and showing the user what was inserted into the input field has a higher priority therefore, because that is an update thats performed independent of our own logic that we perform in our React component.

So this UI update of updating the displayed entered input value has a higher priority now.

So the list now lags behind because by using startTransition and wrapping it around setFilterTerm around that state update tells React that updating this filterTerm state has a lower priority, and therefore, indeed our actions are executed first.

And eventually, the filterTerm state update will finish and the actual filtering of the products will then therefore be performed with a delay.

Thats why this lags behind and why we see the old UI or this part of the UI for a longer time period, instead of updating both the displayed input value and the list simultaneously, which on the upper hand led to the laggy input feeling into slightly unresponsive input.

And thats exactly the idea behind these Concurrency features introduced by React.

The perceived performance can get better because key user interface elements, like the input element here stay responsive and other parts of UI might lag behind to give the user an overall smoother experience when using our user interface.

Because it typically feels worse if youre typing and you dont see your characters showing up in the input field than it does to wait for a list to update.

And thats exactly the idea behind Concurrency features and the startTransition function.

So of course, six times throttling still is a lot so its not butter smooth, but its better than before.

And of course, if I go back to just four times throttling, its way faster, way more performant.

And if you would remove startTransition again, so if I just set my FilterTerm again like this and I comment out this code, then you will notice a difference.

So now Im not using startTransition anymore.

And now its, again, way more laggy especially when clearing entered values here.

You, of course, still dont see when Im hitting the key to delete a character, but I can tell you I hit it and I have to wait quite a bit until the character is deleted here.

So that was pretty laggy.

And if we go back to the code that uses startTransition again, and that therefore signs a lower priority to updating that filterTerm, this gets better.

Now why does this get better? Because now the update of whats visible here in the user input field is decoupled from the update of the list of data.

Updating the list of products is treated with a lower priority because the filterTerm is responsible for changing that displayed list of products.

And we are treating that filterTerm or the update of the filterTerm with lower priority.

Therefore, the UI update related to the input field is actually treated with a higher priority than the UI update related to the list down there.

And you can also tell that this is the fact if you watch the input and now if I clear it, you see the list lags behind.

We still see the old data there after the input was cleared because updating that list is treated with a lower priority.

And if we, again, switch back to the mode without start transition.

So if we dont prioritize our updates, then you will see that now we dont just have the laginess, but also the input field and the list are updated together because both updates have to be performed together because were not telling React that one update would be more important than another update.

And with startTransition we are doing that.

So thats the startTransition Hook in action.

I already wanna say right away that you should not start wrapping all your state updates with startTransition though.

Only use it if you have scenarios like this where you have a slow laggy user interface, especially on older devices maybe and where you have no other solution you could use.

So where you cant perform the work on the server or somewhere else, then this is a good solution.

But otherwise, you should not start wrapping all your state updates with startTransition, because of course this also takes up some extra performance and is some extra work to do by React.

So therefore, you should use it with care in certain niche use cases.

And this, of course, is a made up demo here just to show you how it generally works and to introduce this feature in general.

Thats why this is, of course, kind of an artificial demo but you can hopefully see how startTransition works and what it does for you.

Now what does isPending do? Well, as mentioned, this tells you whether there currently is some state updates thats still pending.

So that hasnt been performed yet by React because its treated with a lower priority.

And you could use isPending to update the UI to show some fallback content whilst youre waiting for the main state update to complete.

For example, we could use isPending to show a paragraph where we say Updating List, like this, below the input field.

So now if isPending is true, we will show this paragraph.

And we can see this in action here.

If I now enter something and especially if I delete it, we see updating list here and then after a while this disappears and the list then is updated.

Might be a bit hard to read because its black on a dark background, but of course, we can quickly add a style here quick and dirty in some inline style so that we can see this better.

Now here you go.

Thats our feedback.

So thats all the feature you can use when using useTransition.

Now React 18 did not just introduce the useTransition and startTransition, but all the useDeferredValue.

And useDeferredValue is all the related to this Concurrency feature and to this kind of problem we have here with a laggy user interface.

Now useDeferredValue is used in a slightly different way though.

According to a React team member, Dan Abramov, who you should absolutely follow on Twitter by the way if you wanna get the latest insights information about react.

According to him, useDeferredValue is mainly useful when the value comes from above and you dont actually have control over the corresponding setState call.

If it would, for example, be coming from some third-party library or some package thats doing that for you.

Of course, in this demo app, we do have control but we can still simulate that we dont have control.

Lets switch to the ProductList component where we get the filtered products.

Here, we dont have control over the state call inside of this component.

And yes, we could switch to App.js but lets assume that we cant do this.

Then in here, in ProductList, we could use this useDeferredValue Hook to still show some fallback whilst we are waiting for the state to update.

For this, we can import useDeferredValue from react.

And then we can use this here to get our deferredProducts by calling useDeferredValue and passing our products as a value to this Hook.

And then we wanna use the deferredProducts down here for mapping them to this list here.

With this change made, if I also get rid of this isPending check here and I disable startTransition here in App.js and instead I set my FilterTerm as we set it initially in this demo before we add it useTransition.

So if we switch back everything here in App.js, but we use useDeferredValue in ProductList, then if we go back and were still throttling here, you will notice that now we have a similar behavior to what we achieved with startTransition and useTransition in general.

If I clear the list, the input field updates instantly, but the list lags behind and we still see the old value until the list was updated.

So useDeferredValue allows us to achieve a similar result if we dont have control over the state call that is related to the laggish behavior.

If we do have control over it, using startTransition and the useTransition Hook in general is preferable as its seems.

And thats it for these two Hooks and this new feature.

Were still in the early days of Concurrency though, so well have to see which patterns and best practices emerge, but this video could hopefully help with getting you started with these new Hooks and help you understand these hooks and what they do.

I will, of course, create more content on these Hooks and on React in general as things change and evolve.

But this video hopefully was helpful as a start.

Academind: useTransition() vs useDeferredValue | React 18 - Web Development