Today I will discuss some of my observations on concurrent rendering in react which is still in experimental stage.
React and its libraries are fully written in javascript and this whole bunch of javascript runs on the main thread. The main thread also runs DOM updates, browser paints etc. Mainly javascript runs on a single main thread, which makes the react render and updates blocking.
Use Case 1:
Think of a video streaming app. When some streaming api call is done and wants to execute the callback associated with it, but could not execute at that time as some other react related (rendering of some complex nested component) is going on.
It has to wait for the react to finish rendering and then execute.
Use Case 2:
Think of a user input component, when user types a key, a re render of a complex nested list needs to be done. The user will be stuck between 2 key presses, which leads to jarring experience and bad UI.
This happens because the browser needs some empty space or slot in the main thread at that moment of time to print the input and show it to the user, but it is blocked by that react heavy re render.
Concurrent rendering came into play to solve the above issues.
Basically react decides which task is high priority and which is low and accordingly update its components. User input component re render is taken as high priority than complex nested list ongoing render and pauses this render for a while which lets the user see what he/she is typing, and then continue with where it left with the heavy re render.
This is the concurrent way of working.
Let's understand through a code example
There are 2 cases taken into consideration, one is "blocking rendering" (currently how react works) and other is "interruptible rendering" (concurrent react).
Consider a huge list of nested components (around 100) and also 50 such sibling components.
Every Element
component gets a count value from its parent. The root component App
decides what would be the value of the count. After every 10ms, we tend to set the new count. Have used setInterval for this and it will be started after button click. This interval will be cleared after 5 seconds of button click.
After clearing we will see how many times 'actually fired'
is consoled on the browser.
The purpose behind this is to find how many times setInterval callbacks are fired within a specific timeframe.
Also find codesandbox link below the explanation
Synchronous Rendering
Use the common render function in index.js file
ReactDOM.render(<App/>, document.getElementById('root'))
App js file
class App extends React.Component {
constructor() {
super();
this.interval = null;
this.state = {
value: 0,
};
}
handleClick = () => {
let startTime = Date.now();
this.interval = setInterval(() => {
if (Date.now() - startTime > 5000) {
this.handleStop();
return;
}
console.log('actually fired');
let i = 10000;
// complex operation or some api call which sets the state after its completion
while (i > 0) {
i--;
}
if (i === 0){
this.setState((state) => {
return { value: (state.value + 1) };
});
}
}, 10);
};
componentWillUnmount() {
clearInterval(this.interval)
}
render() {
return (
<div style={{ fontSize: 16, lineHeight: 1 }}>
<button onClick={this.handleClick}>Start interval</button>
<div style={{ display: "flex" }}>
{
heavyList.map(() => {
return (
<div>
<Element value={this.state.value} nestingCount={100}/>
</div>
)
})
}
</div>
</div>
);
}
}
Element
component nested 100 times using recursion
class Element extends React.Component {
render() {
if (this.props.nestingCount === 1) return null;
return (
<div style={{ marginLeft: "0.4px" }}>
{this.props.value}
<Element
value={this.props.value}
nestingCount={this.props.nestingCount - 1}
/>
</div>
);
}
}
Now there are 5000 elements on the screen as you can see.
Click the Start interval
button and wait for 5 sec and notice that the setInterval callback was called ~37 times and this can be confirmed by number of times 'actually fired'
is consoled in the browser.
It may show different count on different browsers. But the point is to show this count in relation to the next(concurrent) type of rendering.
Latest Element
count will be ~37 on the screen.
This is because as explained above heavy rendering is blocking the main thread and react can not acknowledge the setInterval callback unless its render part is done.
Lets look at profiling of synchronous behaviour
As you can see in the above chrome profile. The red boxes marks the calling of the setInterval callback and the corresponding setState. This callback is finished executing only after whole render tree is updated, paints it on the browser and then is able to give space to the upcoming callback.
Concurrent Rendering
Just change this in the index js file
ReactDOM.unstable_createRoot(
document.getElementById('root')
).render(<App />);
and do the same thing, click the button, wait for 5 seconds and see the magic.
This time actually fired
is consoled ~150 times, and the latest Element
count is also ~150. This means the setInterval is acknowledged more number of times this time.
This is possible because react pauses
the heavy rendering and prioritise the new state as more, and works on it and comes back to where it left.
Lets look at the profiling of concurrent rendering
As you can notice the red boxes in the above profile, which represents setInterval callbacks are executed in between the renders (concurrent behaviour). The render process is actually interrupted unlike in above example.
I tried the same examples with an object animating using requestAnimationFrame() along with this counter. Got smoother animation in concurrent mode when compared to that using normal case.
Switch between concurrent and the usual reactDom render in the index file.
Click on the box to start animation and click the Start interval when the animation is happening. You can click on the box as many times in 5sec to see the effects.
Open in separate window if animation is not working here.
Top comments (0)