Louis Zawadzki

React for React Native

Transcript

Louis

All right, thank you Mo for the great introduction. So, hey, my name is Louis Zavadsky, I work for Doctolib, and I'm very excited to be back here because I was actually on stage two years ago, talking about performance issues. And last year, we had Tomasz Zawadzki talking about new architecture. So if your surname also happens to be Zawadzki, please do apply to this conference. I think it works very well. Okay, I think we have a good audience for jokes today. That's great. So React for React Native. What's that supposed to mean? My goal today is to show you how React Native uses React, how these two libs work together, and more concretely, how you can use new concurrent features that come with React 18 and 19, namely transitions and suspense. So, to get us started, I'd like to play a little game with you. So I'm going to show some APIs that I'm sure you know. And I want you to guess whether it's in React or in React Native that they are implemented. So if you think this is in React, clap your hands. If you think this is in React Native, don't do anything. Are you ready? Okay, first one, use date. You've been trained. That's very good. Indeed, it's in React. The next one, the view component. Did you hear what I say? Yeah? Okay. So, yeah, right, it's in React Native. And the next one is going to be a little bit trickier, so I'm going to explain exactly what it is. So it's automatic batching. So, what is automatic batching? So let's say you have this component that has two different states, a count and a flag, and what's interesting is that when you press a button, inside the setTimeout, so asynchronously, you set both of the states. So, since React 18, this will only print one log, one extra log, because the two state updates will be done at the same time. Whereas before React 18, you would get actually two different render phases, and you would get one render phase to update the count, and another one later to update the flag. So, once again, if you think this is in React, clap your hands. If you think you think -- wait, wait. If you think this is React Native, don't do it. And if you are not sure, just clap one hand. Okay. React. Indeed, just like you, I thought it was in React, and when I run this code on my browser, this is what I got on the web. But then I tried it on my React Native application, and I still got that with the exact same version of React. So, I was a little bit puzzled. At first I thought there would be something like that in the React code. But actually, no, that's not the case. So I dig a little bit further and I found these discussions on the React Native community discussions and proposals. Just a group of people discussing whether batch updates and automatic batching is available within React Native. And they're just asking whether it's available or not, and it ends with this comment, where we have VKS-GOTAM-1986 telling us that Newark supports it, and that's it. End of the thread. By the way, if you're in the audience today, you have to know that it is because of you that I made this talk. So, yeah, at this point, I'm even more confused. I don't really know why is it working, but only on the new architecture. It's supposed to be in React. So to understand how that works. First, we need to understand what's going on when we update a state within our application. So when you trigger a state, you start what is called the rendering pipeline. So for those of you that are in the audience, you probably have heard a talk from Anna today talking about that. If you're watching on YouTube, you can find the video up there. I hope this will work. So this is what happens when you render your component. And it starts by something that's called the render face. So I know it's the same word, but it's something different, so it's a bit confusing. But here we start by something called the render face. And so to simplify it in the render face, React will create a React component tree, which is a tree of plain JavaScript objects. And then it passes that tree to the renderer, so the renderer which is implemented in React Native. And that renderer will build a shadow tree. So the shadow tree is built on the native side. So that's the important part, is built on the native side. And once it gets this shadow tree, we move on to the next phase, So the next two phases are not going to be so important for us today. So I'll go very quickly through them. But we have the commit phase, where we do some tree promotions. So we mark the tree as being the next one. That's in the commit phase. And then we have the mount phase, where we actually render the components in the host platform, so on iOS or Android. And if you want to know more about that, you can watch the AirCheck talk here on YouTube. All right. So now that we know how it works, let's have a look at how it behaves when we have the case of automatic batching. So when you trigger your step-update, what React does is that it schedules an update on the fiber. So if you don't know what a fiber is, it's a unit of work that's supposed to, that's linked to one node, so like one component. So you oversimplifying it, it's computing the state of your component for the next render. And then you have two different paths actually. The first path is the one that takes a React if your renderer is configured to be a legacy renderer. So what React does in that case, it will flush the work on root. And what that means is that it will finish the work for all fibers, so it computes the whole tree of your app, and then it tells the renderer to commit the root. So straight away, we commit the root and we go through one rendering phase, what we call the rendering phase in our component lifecycle. And so when the next update comes in, it goes again through the same flow. So that's why we have two renders. But there is another flow. And on that flow, which is the concurrent renderer flow, we ensure that the route is scheduled. And that's a little bit different. So what we'll do is that we'll finish the work on all fibers, and then we'll wait for the call stack queue to be empty before committing the route. When we think about our example with automatic batching, what will happen is that since we are on the same function, we'll be still in the same call stack execution. So we'll wait for the next update to come in, and then the commit route will include our two state updates. And you've guessed it, the old architecture is using a legacy renderer, whereas the new one is using a concurrent renderer. But why is that so? To understand that, let's look a bit at what is a concurrent renderer. The main feature of a concurrent renderer is not to get automatic batching, whether it's nice, but it's not the main feature. The main feature is actually that it can interrupt the render phase for a new render, if you have a more important rendering. And it can do so whether the rendering is happening in React, or whether it's happening in the renderer when it's building the shadow tree. Now, let's look at how the old architecture works. So with the old architecture, there are two key things that we need to look at. The first one is that when it builds a shadow tree, it's called a native API. And because on the old architecture, to call native APIs, we have to use the bridge, it does so asynchronously. And furthermore, the second key point is that the shadow tree, when it changes, it's mutated. So we mutate, actually, the nodes of the shadow tree. Now let's try to imagine how concurrent renderers and the whole architecture would work together. So let's say I have a first render face, and I start building the shadow tree asynchronously. And then there is a more important update that comes in. And so what I would have to do at that point would be to asynchronously tell my native site to revert all the changes. So by that point, actually, it might already be finished, and I've committed everything, but I'm still trying to send an asynchronous message. Then I need to revert all the mutations, and then send back another asynchronous message to my JavaScript player to tell that now it can start the new render. So as you can see, is it going to be as fast? Not sure. And also, it could be the source of many bugs. I would not like to be the engineer on beta tasked with doing this. Now, why is that possible? to do the automatic batching with the new architecture. So the new architecture introduces a new renderer, which is called Fabric. And so thanks to Fabric and GSI-- so GSI enables the now synchronous communication between the JavaScript layer and the native layer. And so when we have an update that wants to cancel a previous update, now it's all synchronous. And the other key thing is that the ShadowTree is now cloning, the ShadowTree construction is now done by cloning the ShadowTree before changing it, so there's no mutation anymore. So it's much easier to cancel it. It's not a coincidence actually that it works so well, as you see. It is not a coincidence that it works so well, because Fabric was actually built with that intent from the start. So let's recap a little bit what we've seen so far. The old architecture renderer is async and mutates the shadow tree. On the other side, we have the concurrent rendering that can interrupt renders. Therefore the old architecture cannot use concurrent rendering. But, automatic batching only works for concurrent renderers. And so finally, that's why automatic batching does not work on the architecture. There's one thing that here you might be a little bit curious about, and that I didn't explain yet, is why automatic batching only works for concurrent renderers. So, I tried to look, I don't have the answer. So, I was asking, well, could automatic batching work for legacy renderers? And I also asked the next question, which is, should we do it? And I'm not sure we should do it, because actually you have a function that's available in React and React Native that's called UnstableBatchUpdates, and that you can use to tell React and React Native, specifically React, to wrap your state updates, to tell them to put them in a batch. So, if you're using the old architecture and you have some issues with automatic batching, you can use that function. Okay, let's have a little wrap-up of this first part of the talk. So I think it's really nice to see that the new architecture is not only here to get performance tweaks and faster native API calls. I think it's great to see that it also enables you to get whole new features of React available to you. And talking about concurrent rendering, so I told you that it could be interesting to use it to cancel some render faces, but you might be thinking, "Okay, what is this case? When can I interrupt a render face? How can I do that? And what is it about these new features like transitions and suspense?" So let's start with transitions. So transitions came out in React 18, and we're going to look at an example actually without transition to start. So let's say I have this app that enables me to look at a list of Pokemons, and I can also look for some Pokemons. I can filter them by type, and whenever I type something in the search, I want the result for to contain then my query term. How is this implemented? So I have three states. I have two for my search terms, so either for the thing that the user typed in the input, or for the selected types. And I have a third state that is for my list of Pokemons. And then I have two actions. They do the same thing. They first update the state for the filter, and then they update the list of Pokemons based on the filters. Okay, let's have a look at how it behaves. And unfortunately, my list is actually very slow to render because it uses a lot of SVGs. And what you can see here is that the UI is not really responsive. Whenever I click on something, it takes a while to render. And also, every time I type some letters. I see all the individual states for every letter, but I have maybe typed more letters, and I don't care anymore about all the intermediate states. If I type pi, I just want to see the result for pi, not for p, and then wait another second to see pi. If we look at our threads, what happens here, first we set the search query, so we start the render phase, and then we set the Pokemons. The two get batched, so we go into the render phase of set Pokemons that is very long. And while we are doing this render phase for said Pokemons, comes new step-updates for selected types and Pokemons again. But we have to wait for the first one to finish, and so we have to wait for the end of the render phase, for the commit, for the mount, before we can start actually the render phase for selected types and Pokemons. And if you do that multiple times, you just queue more and more updates. So that's the issue that transitions is trying to solve. So if you want to use transitions, what we need to change is really one thing. We need to wrap the setPokemonsStateUpdate by this startTransition function. So you can get this startTransition function in two ways. Either you can directly import it from React, or you can get it from a useTransition hook. And this useTransitionHook has the benefit of giving us, as well, a Boolean that I called here isPending that can tell us when we are actually rendering something inside a transition. So I'm going to use this isPending state here to display three dots after the result for string. And what this start transition will do, it will mark this state update inside of it as non-critical for React. And so React will know that it can actually interrupt this state update for other state updates that might come in. So let's have a look at how it behaves now. So you can see whenever I type something or click on the filter, it updates the UI automatically. And when it renders the list of Pokemons, now I get only the result for the last thing that I searched. It's actually really nice. When we look at our threads, what happens is that we set the search query, and then we get the setPokemonStateUpdate, but because it's marked as non-critical, React will first finish all the rendering for the previous states. So we'll go through the render phase, commit, and mount for the setSearchQuery, and we start setPokemon, and then when the setSelectedTypes will come in, React will know that it can console setPokemons. and go on with this one. And then when we have the new set Pokemons, React will know that actually it can totally forget about the first set Pokemon because we have a new state update for the same state, and just renders this one instead. So for those of you who would like to indulge into extreme sports and are already using React 19, you can know that now you can accept async callbacks inside start transition. I think this is interesting if you want not only to use this ispending Boolean, not only for your rendering, but also if you have some async API calls going on there. Let's move on to the next feature that will benefit also for concurrent rendering, which is suspense. Just a quick check before I start with suspense. Can you clap your hands if you thought that you could use suspense with the old architecture? Okay, actually, you're right. You can use suspense with the old architecture. Let me show you how. A quick example, so from our Pokemon list, here I can use the use suspense query hook from React Query. It has the same API nearly as the use query hook that you probably know, except it doesn't give us a loading and error state. Instead to display a loading state, I have to wrap my component. my Pokemon list by a suspense component, and give to this suspense component a fallback prop that will be what I will display while this use suspense query hook is loading. So how it works under the hood is actually quite simple. This use suspense query, while it's loading, it will throw a promise. And the suspense component will catch that promise and only display the result, only try to render it when the promise resolves. Now, what do we get with concurrent rendering? So with concurrent rendering, React will mark any updates that will happen inside the suspended component as non-critical. So it means that anything that happens while you are rendering this non-critical, this suspended component, will get priority over it. So to demonstrate the impact of that, we use a small moving box that will simulate any user interaction. So let's look at how it behaves in the old architecture when I'm rendering my Pokemon list that takes a while to render for the first time. So what you can see is that the moving box stops for approximately two seconds before it renders, and then for another two seconds. Now let's try it with the new architecture. So no code change, just moving to the new architecture. So you can see it takes a lot more time for my list to display, because I'm simulating that I have user interaction every 10 milliseconds. But once it is displayed, it only stops for less than 10 seconds, because it actually needs to do some heavy work. But it's a lot better than on the old architecture. Okay, once again, for those of you living on the edge using React 18, I think that it's a nice improvement if you want to use Suspense with the introduction of the use API. So it's not a hook, by the way, just an API. I won't go into too much details here because it's something that deserves a talk in itself. But really quickly, it's something that enables you to get the result of a promise inside a component and to display a fallback using suspense while the promise is resolving. So once again, I won't go into too many details, but it's something that we've heard of, and if you're not aware, it's now available since React 19. Okay, so to wrap it up, what I want you to remember to get out of this talk is that first of all, the new architecture is not only about having more performant native calls. It also enables you to have more features of React and especially concurrent React. Those concurrent features, I think, are going to change a lot the way we write React code. It's probably the biggest shift we're going to have since React Hooks if it gets adopted a lot more. Two things that you might get from this talk as well, transitions can help you to keep your UI interactive during renders. Then suspense also improves your loading logic and you can have more performance rendering with it in the new architecture. That's it for me. Thanks a lot.

Mo

Cool. Well, thanks for the talk. You're welcome. You know when your screen went blue, just because I've been traumatized by Christoph Magiera from Software Mansion, that one time when he faked his computer doing an update in the middle of a... Did you see that? He was doing a talk, and I think it was App.js, and he faked his computer doing a reboot, and he just went meta-Inception mode. So I actually thought you were faking the blue screen of death. Maybe I actually did. Maybe you did. We'll never know. We will never know. Well, I think we've had a lot of new architecture and fabric talks today, which has been really fascinating, delving into it at every level physically possible. But I think this was a good note to end on with this theme of fabric, which is, okay, what does it mean in terms of the apps that you build and some of the code that you can use? So thanks for going through that. I think most React Native developers haven't used transitions because they couldn't use transitions. So start using transitions, I guess. The first question that came in, and I've actually memorized it because I was looking at it ahead of time, is did you use AI to build your demo app?

Louis

So actually, yes, I used it to build my demo app. So it took me maybe... 30 seconds to get half of it. But actually what you couldn't see is that you cannot scroll the Pokemon list. And there are a few things that I needed to fix.

Mo

So are you a certified vibe coder now?

Louis

I think we can say so, yes.

Mo

Great, great. Are there any other concurrent features from React?

Louis

Yeah, there's one that's also interesting that I didn't mention. It's called the use deferred value. So you can use it, for instance, if we take the same example I had about my Pokemon list. Let's say you want to update the list of Pokemons by making an API call through your use suspense query when you type something. So what might happen at that moment is that you will display a loader once again. while you're typing, and maybe you want to instead display the previous value that was already displayed. So useDefaultQuery enables you to do that, and I think it's a really cool API.

Mo

Cool. Somebody's asking, because everybody loves to know what other people use in other people's apps, is are you already using these new React APIs in production?

Louis

They say, "Other than use Query," which you haven't talked about use Query.

Mo

Yeah, unfortunately, no, not yet, because we've not moved to the new architecture, but I could potentially put that Pokemon app in production and say yes.

Mo

Coming soon to an app store near to you. Pokédex. So, let's keep an eye out for it. I figured, okay, I'm gonna just read this verbatim. I figured nobody used, nobody used, okay, sorry. I figured nobody used useEffect today, what's happening?

Louis

I think maybe people are realizing that useEffect is only meant to synchronize your, as it says, your React states with effects, some stuff outside of React. And so maybe it's a good thing that we are using less good useEffects.

Mo

UseEffects, yeah. I mean, library maintainers are using them more for data fetching, so most people will typically use React Query instead of that. And then most of the use cases where people would use useEffect are kind of gone now. Besides the APIs you showed us, which are the ones that you're most excited about coming into React 19 for those living on the bleeding edge?

Louis

So other than the ones that you showed us, is there anything else you're excited about? I think, yeah, I'm also excited about what's going to come next after that. And I'm also excited to know whether the Suspense API, which I think is really nice because it enables you to completely decouple your component. That's now just the happy path and you don't have to care about error management and loading states. I'd be really happy if that gets adapted a bit more by the community and if we see that more of a standard.

Mo

Very, very cool. Out of curiosity, and I can't see anyone, can we get some light on the audience? Is that possible or no? Cool, no light on the audience. Oh, look at that. And now you're in the light. I can see who was asleep. Out of curiosity, who's actually used-- well, who's on the new architecture? OK. Who's used transitions within their app? So a very small subset. Who's been using Suspense new or old architecture? And Suspense on the new architecture with concurrent rendering? So we're really dealing with a very, very, very, very small subset of the group. That's like two Margiela folks sitting here being like, yes, me! So we're dealing with a very small subset of people. So I think all the stuff that you've talked about today hopefully should get people going away, and once they are on the new architecture, actually implementing them in practice.

Louis

Yeah, I hope so.

Mo

Awesome. Thank you so much, Louis, for taking the time, doing the talk. Really appreciate it. Thank you.

Edit on GitHub