In this article, we are going to talk about tips and techniques for making our react projects' performance faster and more user-friendly.
by reading this article you meet the other react hooks and finish the hooks journey that we started before, and you also update your react knowledge by reading about new react 18 features, and at the end, you learn so many techniques for optimizing your react projects.
1. useTransition()
This one is one of the coolest hooks that was introduced in React 18 and it's really helpful!
If I want to explain it, I would start with an example:
Imagine you have thousands of products that you are showing on your page and you have a search input that filters those thousands of products by typing and it shows the related result by pressing any button on your keyboard, the whole filtering process is going to start again to show the updated result and now the problem is we have too many products that cause our filtering process to take much more time and that makes our input functionality slow or in other words, The longer the filtering process gets, the later your pressed letter is going to show up in the input and you may know this problem as being laggy.
you can check out this demo to see what I am talking about. just try to search 4444 to filter the products and then, remove the 4444 at ones from the input. you will notice that it takes a few amounts of time to remove 4444 from the input.
This process in react is like this: we have a query state to set the value of search input onChnage and the value of state is passed to the input(state changes and input gets updated) and also we have a state that contains our products and inside search input onChnage handler beside setting query state we also filtering products and setting products state to the filtered products:
Now, what is the main reason for having laggy and not user-friendly search input?
React tries to update all states and then just re-render the component and show the updated UI with all changes at once. it means even though query state updates much faster because it does not require any special process or anything like that but it has to wait until other states like products state that require expensive process and takes more time to get finished and then at the end, updated query state and updated products state get passed to the screen. by knowing this process, we can understand that all states are urgent in react and none of them has low priority and react re-renders the component one time with all new state changes.
Note: in the past, most people used to fix this laggy problem by limiting the data length on the page and implementing features like pagination to make the filtering process much lighter and fast, But what about nowadays?
Concurrent rendering
Now, React has a hook for this problem which is useTransition and beside pagination, this hook makes react to be able to have non-urgent states:
We want any states like query state that does not require any process, to get updated and be shown up on the screen and don't have to wait for other states' updating process, and then, whenever these heavy states get updated, they can be shown up on the screen which means we want react to be able to re-render the component multiple times which is called "concurrent rendering".
In the real-world example, it is like me having a plan to write this article, and meanwhile, I have to eat lunch. So do you think this makes sense that I finished my article, But I don't publish it just because I'm waiting for my lunch to get ready and then I eat my lunch, and just by eating the last piece of my lunch, I publish my article so this way I finished both of them at the same time!! Well, it does not make sense at all. with the concurrent option, I can write my article, and meanwhile, I put my lunch into the oven to get ready, and as soon as I finish my article I publish it and don't wait for lunch to get ready because it has low priority now! so whenever lunch gets ready I eat my lunch. this way everything is faster and better right?
Note: you need to use the new method of react-dom to make react able to use the concurrent rendering option:
So how should we use useTransition hook anyway?
This hook returns an array with two items: 1. isPending, 2. startTransition
The "isPending" item is boolean, and its value is true until our non-urgent state gets updated and we can use this item for showing loading stuff in UI to have a better user experience.
The "startTransition" item is a function that accepts a callback and inside this callback, we set all the states which should have low priority to make react understand that it should not wait for these states to get updated and they are non-urgent states, and it can render component first when urgent states get updated and second when these non-urgent states get updated:
you can check out the demo to try this and see how better it is. there is no laggy input or things like that and also we have a loading for non-urgent state updating:
Note: remember to use this hook only in these situations, because react does some extra things to implement concurrent rendering and make those states non-urgent, So your state might not be worth the pressure of that extra work and make the performance even worse!
2. useDeferredValue()
This one does the same job as useTransition does, but the difference is that we use useTransition when we can use the setState function inside our component and there are times that we just get the state as a prop and we don't have access to the setState function inside our component so thas is the time that we use useDiferredValue hook to make that state non-urgent.
This hook only accepts one parameter and that is the state:
Note: remember that we don't have isPending option with this hook, so try to use useTransition hook as much as possible instead of useDeferredValue hook.
3. useMemo()
Imagine we have component like this:
We have a function named greetingFunc and this function performs an expensive process and returns a string with the name argument and we have a greeting variable that is equal to the returned value of greetingFucn (basically, every time we define the greeting variable, we are calling the greetingFunc, and giving it a name state to go through the expensive process and return the value we want ) and we also have a theme variable that depends on the darkTheme state value and changes the UI style by changing the darkTheme state.
Now, if we change the darkTheme state by clicking the change theme button, react is going to re-render the component and it means that the greeting variable is going to be declared again, and calls that greetingFunc and gives it the same name state which has not changed at all! ( in other words, by changing the darkTheme state we are also calling the function with an expensive process that has the same input and same output as before!). So we want to call that expensive function just when its input is different and avoid the unnecessary expensive process. we want to memorize the returned value of that function, so if next time it was going to get called again, it compares the input that it receives and if it is different from before, then it can be invoked again otherwise not.
That is the job that useMemo handles. useMemo memoizes the returned value of our expensive function and if next time react wants to call this function again, it compares the old input and new input which you can assume input as a dependency and if the input value has not been changed, it means the returned value is the same, So useMemo hook memoized it already ;)
useMemo hook accepts two parameters, first, a callback that returns the function we want to memorize, and second an array of dependencies to tell react whenever these dependencies values have been changed, react calls our function, and goes through the expensive process:
You can check out the demo and try this, ones by using the useMemo hook and ones without the useMemo to see whenever you change the darkTheme state, does greetingFunc is called again or not?
4. useCallback()
There are two main reasons to use useMemo and useCallback hooks :
- Referential equality
- Computationally expensive calculations
We talked about the second one (how we avoid expensive calculation processes with useMemo hook). So useCallback hook's job is to handle the first one ( referential equality ).
Let's start with an example:
As you can see in the above example, there are times that we pass a function as a prop to the childComponent which is DummyButton in our example, now if you change the state in the parent component with the increase button what would happen?
The parent component is going to get re-rendered again, and that causes our onClick function (which we pass to childComponent as a prop) to get created again. so in javaScript when there are two functions or objects which look like each other they aren't actually equal! because they have a different reference in memory, and by that, it means the onClick function is different from before even though the output and everything are the same and whenever the childComponent props get changed, react is going to re-render the childComponent again, just because the reference of the new prop is different from the old one and that is the Referential equality.
That is the time for the useCallback hook to show up, just like useMemo, useCallback receives two parameters, first, the function we want to memorize, and second, the array of dependencies. the only syntax difference is in useCallback we don't return the function inside the callback parameter, we give the target function as the callback ( in useMemo we pass it a callback which returns the target function). So, by using the useCallback hook, whenever the parent component gets re-rendered, react is going to compare the old and new dependencies values in the useCallback second parameter and if they are different it is going to create the function again with a different reference that causes the childComponent to get re-rendered again and if the dependencies have not been changed so there is no reason to create that function with new reference and re-render the childComponent again.
The above example can be fixed by using useCallback hook like the below image, and also you can try it online by clicking this demo to add useCallback and see how it works:
Note: the difference between useMemo and useCallback is that useMemo memorizes the returned value of the function to avoid invoking ** that again and fix the "Computationally expensive calculations" and useCallback memorizes the **function itself to avoid creating that function again and fix the "Referential equality".
Note: use these two hooks just for these two reasons that we talked about above, otherwise if you wrap any cheap and little functions with these two hooks, react is going to compare dependencies every time and it's just not worth it for small things, and this causes performance issues
5. React.memo()
When we have a bunch of child components inside the parent component, by re-rendering the parent component, all of its child components are going to get re-rendered again even if their props haven't been changed or even if they don't receive any props, it does not matter, react is going to re-render them anyway and this makes performance sad!
react must compare the props of the component before re-render to avoid unnecessary re-rendering and if the new and old props are different so then react can re-render the component, otherwise not, and we can do this by using memo.
react.memo receives a callback which is the whole component that we want to memorize. when we wrap our component with react.memo, react is going to compare component's props every time and avoid unnecessary re-rendering.
In the above image, we haven't used react.memo, so whenever the App component gets re-rendered by changing the state, react is going to re-render the ChildComponent again. To fix this problem with react.memo we do it this way:
you can try it out by clicking this demo and use the above example ones with memo and ones without it, to see whenever you update the state by clicking the 'update parent component' button if ChildComponent gets re-rendered again and 'child component got re rendered again!' text logs again or not?
6. Code-Splitting with lazy & Suspense
When we want to use a bunch of components in our component we just import them in order to use them, and importing the components is completely static, and components get imported at compiling time and we can't tell react to load that imported component in parent component just whenever we need it or in another word, we can't make it dynamic import to avoid time-wasting on loading the components that user might even not scroll down to see those components.
one of the most use-cases of this is when we are defining different routes in the App component and importing all page components to use them for each route, and we want to load each page component whenever the route is the one we gave it, and otherwise react is going to load all of them at ones without caring about paths. And that is time for code splitting by using lazy and suspense that makes us able to load components dynamically and whenever we need it.
lazy and suspense help us to load component whenever we need that specific component, so this way we don't have to load all of them at once and it helps performance a lot:
In the above example, we are importing Home and Panel components dynamically and whenever the route is ' / ' home component is going to be loaded and whenever the route is ' /panel ', panel component is going to be loaded.
lazy receives a callback that returns an import method and the import method receives the component path in the project (5th & 6th line in the above example).
All the components that have been imported with lazy, should be wrapped with suspense and the suspense receives a prop named fallback and fallback value is a JSX and it is for loading purposes to show the user a loading until the requested component gets ready and loaded and that is really a good user experience.
7. React Lazy Load Image Component
Let's say we have a page and we get 200 images from the server to show on that page and whenever the user navigates to that page, it sends HTTP requests and loads all 200 images one by one and that is going to take time to load them all, while the user might not even want to scroll down to see at least 10 out of 200 !! so why should we load the images that don't show up on the screen yet?
In this case, we use a library called " React Lazy Load Image Component " and its job is to fix this performance issue by loading the images dynamically and whenever we need it and also we can use features like placeholder or effect for our images to show the user a blurry effect or any picture we want when the images are too heavy and not ready to be loaded.
We use React Lazy Load Image Component library like this:
You can checkout the whole document here
Well, that's it! these were some of the coolest tips and techniques to improve our react projects' performance and have a better user experience. if you use them carefully, you are going to be much better react developer.
This article can be called " Performance optimization tips " and also " React hooks : part 2 ".
Goodbye and Good luck🤞
Top comments (1)
Useful Article 👏👏