Why Props References Break Optimizations in React + React Native

Careful with props references when writing optimizations

Kush Tran
Kush Tran
Jan 9 · 8 min read
Image for post
Image for post

React is great and fast most of the time. But sometimes, due to heavy calculations, it slows down, that’s when we need to measure and optimize our Components to avoid “wasted renders”.

Optimizations come with its cost, if it’s not done properly, the situation might get worse. In today’s blog post, we get to know the rendering process, learn the cause of wasted renders, solutions & how it’s broken.

Table of contents

What is “Rendering” ?

Rendering is the process of React asking your Components to describe what the section UI looks like, on the current combination of Props and State.

Process Overview

During the process, React will start at the root of the component tree and loop downwards to find the components flagged as needing updates. For each flagged components, it will call render()(for class components) or FunctionComponent()(for function components), and save the render outputs.

A component render’s output is written in JSX. Either from render() or FunctionComponent(), the output eventually becomes ReactElement. These elements are used together to form the virtual tree (tempt tree).

Image for post
Image for post

After collecting the new tree, React will diff it, collect lists of all changes need to be applied to make the real tree look like the current desired output. This process is called Reconciliation.

Above is very basic process to create Host tree (the tree output). The host tree can be vary of types, base on different platforms (web, mobile,…). Dan Abramov wrote a great explanation for it Here.

React team divides above work into 2 phases:

More layers for React Native (You can skip this and move on if you’re not interested)

React Native creates a tree hierarchy to define the initial layout and creates a diff of that tree on each layout change like above. Except React Native manages the UI updates through couple of architecture layers that in the end translate how views should be rendered.

1. The Yoga layout engine

Yoga is a cross-platform layout engine written in C which implements Flexbox through bindings to the native views (Java Android Views / Objective-C iOS UIKit).

All the layout calculations of the various views, texts and images in React-Native are done through yoga, this is basically the last step before our views get displayed on the screen

2. Shadow tree/Shadow nodes

When react-native sends the commands to render the layout, a group of shadow nodes are assembled to build shadow tree which represented the mutable native side of the layout (i.e: written in the corresponding native respective language, Java for Android and Objective-C for iOS) which is then translated to the actual views on screen (using Yoga).

3. ViewManager

The ViewManger is an interface that knows how to translate the View Types sent from the JavaScript into their native UI components. The ViewManager knows how to create a shadow node, a native view node and to update the views. In the React-Native framework, there are a lot of ViewManager that enable the usage of the native components. If for example, you’d someday like to create a new custom view and add it to react-native, that view will have to implement the ViewManager interface

4. UIManager

The UIManager is the final piece of the puzzle, or actually the first. The JavaScript JSX declarative commands are sent to the native as Imperative commands that tell React-Native how to layout the views, step by step iteratively. So as a first render, the UIManager will dispatch the command to create the necessary views and will continue to send the update diffs as the UI of the app changes over time.

So React-Native basically still uses React’s ability to calculate The difference between the previous and the current rendering representation and dispatches the events to the UIManager accordingly.

Standard Render Behavior

It is important that:

React’s default behavior is that when a parent component renders, React will recursively render all child components inside of it!

For example, say we have a component tree of A > B > C.

Image for post
Image for post

Now, it’s likely that most of the components will return the exact render result as last time, therefore, React won’t need to make change to the real tree. However, React will still have to ask the components re-render themselves and diff the render output. Both of those take time and effort, especially when the components are large & have heavy calculations.

This is how wasted renders happens.

Improving Rendering Performance

Renders are normal expected part of React. It’s also true that sometimes the effort is wasted if a component’s render output hasn’t changed, and that part of the tree doesn’t need updating.

Render should always based on current Props and State of component. If we know ahead of time that Props and State won’t change. the render output won’t change, then we can safely skip the rendering process of that component.

When it comes to optimization, you can make it run faster or do less work. Most of React optimization is about doing less work.

Remember to measure before any optimization, so you don’t commit premature optimization.

React offers three primary APIs for skipping rendering of a component.

All of these approaches use a comparison technique called Shallow Equality. This means checking individual field in two different objects, and seeing if any difference in the contents of objects. The technique compares with ===, a simple and fast way JS engine can do.

Image for post
Image for post

How New Props References break Optimizations

As we learned about techniques with shallow equality above, it’s apparent that passing new objects will fail the comparison because “===” compares reference, even if the contents haven’t changed. That breaks our optimizations, the component still renders, but wasting more diffing effort, diffing through props comparison & diffing tree. Be careful !

Image for post
Image for post

In the example, we pass onClick and data as props to MemoizedChildComponent. Although we optimize ChildComponent, it still re-render every ParentComponent updating. Because MemoizedChildComponent’s props get new objects every time.

We expect MemoizedChildComponent skip rendering because it’s props contents are the same. Let’s move on and figure how we can fix this.

Optimizing Props References

Class components don’t have to worry about accidentally creating new callback object references as much, because they can have instance methods that are always the same reference. However, they may need to generate unique callbacks for separate child list items, or capture a value in an anonymous function and pass that to a child. Which resulting in new objects, React hasn’t come with any built-in to optimize those cases.

Function component, React offer two hooks useCallback (for callback function) and useMemo(for any kind of data like creating objects or complex calculations).

The intention of this post is to draw the problem out, not teaching about Hook, i believe there are many sources explaining those Hook well. So i’m not going into detail here. Maybe in the next posts, who knows right ^^

Memoize Everything?

Apparently NO, every optimization comes with its cost. Optimizing carelessly ends up making the performance worse, always measure first, by React devtool or any of your favorite, find the bottleneck, and then optimize.

It’s not always benefit, if it was, React would make it the default implementation, right ? :D

Kent C. Dodds mentioned a case when useCallback worse here. And my favorite Dan’s tweet:

Why doesn’t React put memo() around every component by default? Isn’t it faster? Should we make a benchmark to check? Ask yourself: Why don’t you put Lodash memoize() around every function? Wouldn’t that make all functions faster? Do we need a benchmark for this? Why not?

Summary

Well, that’s the end of this post.

To summary, Rendering process of React renders children components due to updating parent components, that’s not bad, that’s how React knows the changes. And sometimes the rendering effort is wasted.

Skipping rendering is a common way to optimize this, and the work relates to props references a lot. Optimizing with care, don’t premature optimization.

Props Reference has more problems than that. Recently, i love Ben’s article on how it affects dependencies in useEffect hook.

For any further questions or comments, let me know. Thanks !

JavaScript In Plain English

New JavaScript + Web Development articles every day.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store