Reducing unnecessary React re-renders
Have you wondered how to gain quick wins reducing React re-renders that can make your pages less laggy allowing for more user engagement? this article covers ways to do that.
More often than not writing react components is taken for granted. Everything looks good, the page is loading within a reasonable time but sometimes what appears to be good enough on our powerful developer machines maybe a bad experience on the user’s machine. Multiple duplicate network calls, multiple duplicate calculations and unnecessary re-renders all contribute to a bad user experience and are often preventable. This articles aims to cover some strategies of ensuring we reduce unnecessary re-renders which causes a react component to re-render along with its children. Let’s first understand the problem at hand. Imagine we have a component A that has 3 children (B, C and D). Each of those children components have sub children and so on.  The first time the A component is mounted, it is going to mount all of the children. The same goes for when it is re-rendered, it is going to re-render all of its children. What causes a component to re-render could be state/s changes, some hook reactivity or even the parent props changing. If not designed well, we could trigger queries if any every time we re-render but you can easily protect against that if we check if the data that is going to be queried is already defined or not. Checking for data can be as easy the following: ```jsx useEffect(() => { if (data){ return } loadData().then((_data) => { setData(_data) }).catch((error) => { Log.error(error) // Redirect or do whatever }) }, [data ]) ``` Actual problems occur when we try to re-render unintentionally when we don’t need to. The re-rendering could be expensive and can result in a slow user experience. Hence, the fix is to try to only re-render what needs to be re-rendered. But how do we do this? First try to understand the current component hierarchy. Try to see what really needs to be passed down by the parent to its children and what can be used directly from the children. Do you need to retrieve data in a grandparent component and prop drill it all the way down to children that are multiple generations long (a couple of hops down the hierarchy). If we relate this to the original diagram above, this could be retrieving data in component A for component C1. Is this really necessary? If the data is only of use to component C1 then I would argue to retrieve it in C1 only. This simplifies the dependency tree so that if the data gets updated you will only re-render C1 which is great because it has no children and that means we only re-rendered what we need unlike if it was passed from A which would’ve re-rendered A, C and all of D’s children. Great. There might be reasons on why you may not need to retrieve the data in C1 directly because you’re worried that the component is doing multiple things like retrieving data, logic and rendering the html. I get that but I think there is a better way to retrieve data using custom hooks or data fetching utilities. I prefer hooks for when you are using functional components.  As you can see above. We utilised a hook that will only cause component C1 to re-render. Fabulous, this also allows for the reusability of the hook if need be. You can test the data retrieval separately as a result. You can do that like: ```jsx // Hook file const useDataForC1 = () => { const [data, setData ] = useState(undefined) useEffect(() => { if (data){ return } loadData().then((_data) => { setData(_data) }).catch((error) => { Log.error(error) // Redirect or do whatever }) }, [data ]) return { data } } // Component file const { data } = useDataForC1() ... return (<span>{data.title }</span>) ``` Now we fixed prop drilling for this case. Sometimes you can’t fix it as easily. What if the data was shared by multiple components, see D’s children in the diagram, there is 12 of them. In this case, ask whether the data needs to be truly shared by all of the twelve children. There is other ways where we try to reduce re-rendering to a minimum. If there are 12 states in a component and only 2 are shared by all children. Why do we need to re-render all children if the other states change? I don’t think we need to. This is where using memoization is going to help. We can use “useMemo” for functional components. It is like caching, what you do is render the component within the “useMemo”. You will need to define a dependency array of things that are required for recomputing the memo (component in this case). Usually you will find that not all states are needed for a components. Of course, the memoization process has some cost. I would think that it is often cheaper to just re-render the component if what the component does is trivial in computation. It saves you lines of code and the expensive cost of the observer logic used in “useMemo” used to know when to recompute. There is a similar approach to reduce re-renders when passing in function callbacks. “useCallback” is another memoization approach but for functions. It is useful for things like child to parent callbacks, event handlers and just prop drilling functions. In essence, properly passing down the correct props, memoizing components and thinking whether we truly need to prop drill data if not needed here are questions and considerations to keep thinking about while writing React code.