For this I also have a couple of examples. So for example, there are some libraries out there that register event listeners or register subscribers to some state management solution and they do that during the rendering process. So here we have this fictional component that creates a listener during the rendering phase and directly adds that to the window object. Now imagine this component never unmounts, then this listener will just be stuck on the window object and no one will ever remove that, so we have a memory leak introduced to our application. Luckily, this problem does not really occur too often in applications.
This is more happening in library code and so whenever you stick to popular libraries, you won't really run into that problem, but it's still important to keep an eye on that, that you don't mutate the outside world, don't build up a connection, don't create web sockets and stuff like that because this might break when we use concurrent features. However, it still has to be possible somehow to do these things, to read from global values, to mutate the outside world, because in the end, our application is made to communicate with a server, to communicate with the user, to display some changing state, so there have to be some solutions.
The first one is to just move all of this communication inside of UseEffect because UseEffect is made that we have the build up, so we can make a connection at event listener and stuff like that, and we have the cleanup, so we just return another cleanup function from the UseEffect and can clean up if we build some connections, so we add an event listener and then we remove it again when the component is unmounted. And since we always have this guarantee that there will always be a cleanup after the UseEffect call, it's safe to do something like that inside of UseEffect. So we always have this pattern that you have a state variable inside of your component, you read from the server inside of UseEffect, update the state variable, and then you're good to go. The problem with that is that during the first reconciliation phase, the effect has not been called yet, so whenever you require to display something from the UseEffect call, you have to do the first reconciliation phase without this value because it will only be present in the second phase when the state has been updated.
So the second option is to pick a library that fits your use case. If you pick React Query to do communication with the server side, if you pick something like Redux Toolkit to handle your state management solutions, you can pretty much rely on the popular libraries to already be concurrent-mode safe or concurrent-feature safe because React team has been working quite closely with the maintainers together in this RFC process so that these libraries are already concurrent-mode safe. And the third option is if you really want to handle your own solution is to use useSyncExternalStore. This is a hook that React provides us that we can use to sync our application with some external data. For that, you first have to define a subscribe function, but you again have this notion of a buildup, a constructor, so to say, and to cleanup where you have to guarantee that these will always run in pairs. So here, for example, we add an EventListener to the global resize event and we make sure to remove that again once this sync process is cleaned up by React. After we did that, we can build a custom hook, use windowWidth, for example, and use the hook useSyncExternalStore where we pass in our subscribe function as the first argument, and the second argument is the value that reads from the outside world. And now React can absolutely be sure to control when this reading function is called so that React can be sure that this value never changes during a reconciliation process so that the user interface always stays consistent. That way, we can use our custom hook, use windowWidth, wherever we want in our application, and can use the value during the initial render of our component and can be sure that during updates, it will stay up to date as well and not introduce those tearing issues. That means when we use one of those three options, we can just go back and relax and let React do its thing because the React team designed React in a way that we live above this abstraction level. So in the ideal world, we would never wonder how many times our reconciliation phases are called because React might make some decisions in the future to call them more often, to call them less often, to skip some or to recall them. If React is able to rely that these functions are pure, then React can decide these things for us and can make optimizations without our code breaking. So with these assumptions, it's now possible again to lean back and stay safe in the concurrent world of React. Thanks for listening. Thank you ever so much. Thank you. Thank you. Thank you ever so much. If you'd like to join me here, we've had some questions.
Comments