Skip to main content

React Rendering for Rapidly Changing UIs

This article presents situations where imperative programming can be used to improve performance when updating multiple UI components.

React Rendering for Rapidly Changing UIs

React developers are very familiar with the typical method of updating the UI; update state either via props or internal component state, and React takes care of updating the UI based on that state update. This declarative tech works well and is the standard in post-React frontend frameworks. This article presents situations where imperative programming can be used to improve performance when updating multiple UI components.

Background

There are a couple of downsides to standard methods for React UI updates. First, rendering a component is computationally expensive. When a component's state is updated (via useState or a prop change), React goes through a process called “Reconciliation” to determine the updated UI. Avoiding unnecessary Reconciliation is a standard optimization technique that avoids unnecessary renders and keeps applications snappy.

However, some applications seem to require constant rendering. For example, a dashboard consisting of UI components displaying constantly updating data could be built on a foundation of periodic and almost constant state change based on data updates. This is a perfectly valid technique that may be adequate for the task. On the other hand, another technique is commonly used in game design and works just as well in React.

Game Loops and the React UI

Imagine an application where the state can change almost arbitrarily. It doesn’t matter what is changing the state, what does matter is that any change can happen at almost any time. Additionally, the UI shown to the user must update continuously and as smoothly as possible. This situation is similar to what happens in most video games. The player(s), AI, etc change the state of the game constantly and the game environment (aka the UI) continuously updates based on the UI using a game loop. The following image from gameprogrammingpatterns.com shows a simple game loop.

    Applying this concept to React means we need a way to do the following:
  • First, avoid render-inducing state changes as much as possible by minimizing changes to props and useState variables. Fortunately, React provides the useRef to maintain and update the component's state without causing a component to render. Note, that this does not mean useState and changing props can’t be used, just be aware of any potential consequences that will come from the extra component rendering.
  • Another requirement is a way to update the UI periodically. JavaScript’s requestAnimationFrame exists for exactly that purpose. requestAnimationFrame attempts to run a callback function at 60 times per second. This value can vary depending on the speed of the computer, but the function also provides the information needed to throttle the callback frequency. In React, requestAnimationFrame functionality can be added via a custom hook.
  • Finally, the element that we want to alter needs to have a way of imperatively altering its state.

Now, let’s modify the “game” loop to be more React-specific and discuss what can be done in each step.

A simple UI loop
A simple UI loop
Process Input Doing work to retrieve and or update state can be done here. This includes calling APIs, receiving keyboard events, querying and updating a global store such as Redux using the getState and useDispatch functions, etc. The received input can then be processed so that it is eventually saved to state. On the other hand it may be saved to state directly.
Update State It is possible that this will be done as part of the Process Input step; after-all the results of an API call may be directly saved to state. However, that’s not always the case.
An example of updating state would be changing values created using a useRef hook or updating global state. A global state manager such as Redux could be updated here
Render UI At this point, we do what is required to render the UI. How this is done will depend on the item being rendered, and a couple of different techniques are described in the next section.
Additionally, this step may require accessing values created using useRef or pulling values from a global store using a “vanilla JS” method if it’s available. For example, in Redux the getState function could be used to imperatively get data from a store in a way that does not cause the component to update.

Implementation

There are several ways to implement the UI loop. One method is to update a component state variable (one created with useState instead of useRef) triggering the component to render. The frequency of the update can be controlled using requestAnimationFrame. The rendered component displays content based on the state held in any number of variables created with useRef. Doing this creates a component that will render at a specified interval and displays a UI based on what is contained in any number of useRef variables instead of rendering whenever each of any number of typically useState created variables (or passed props) changes.

Another way that may be available if the UI element that needs to be updated supports it is to update the element directly. For example, updating data displayed in an ApexCharts chart by using the exec function. The imperative exec function could be called via a custom hook that wraps requestAnimationFrame. This results in a chart that periodically updates data without rendering the entire component.

This technique is also frequently used to animate 2D and 3D animations and is frequently demonstrated when working with three.js and the react-three-fiber library which “Reactify”s three.js and includes a useFrame hook to encapsulate requestAnimationFrame. First, note that coding in three.js is imperative; if an object should change rotation or position, we mutate a rotation or position value as seen in the code for this example. Since react-three-fiber wraps three.js inside a React “shell” it could be assumed that the correct way to update components would just be to update the component's state. However, the react-three-fiber recommends against doing that for performance reasons.

Conclusion

Most React developers are familiar and comfortable with the fact that changes to state and prop trigger component renders. However, sometimes, this is not the only way to update the UI. Furthermore, in some cases, it’s not the best or only way to update the UI. JavaScript’s requestAnimationFrame and React-specific derivatives provide an alternative that can be worth trying.


Related Posts