Prevent React from blocking render on an expensive calculation

Let's assume that you have a function performing an expensive calculation in React:

jsx
import React, { useState } from 'react';

const App = () => {

    const [calc, setCalc] = useState(false);
    const [numb, setNumb] = useState([]);

    const generate = async () => {

        // Informing the user that we are calculating the serie
        setCalc(true);

        // Start of expensive calculation
        // React will be blocked at this point
        let array = Array(2**15).fill(0).map(num => Math.random());

        // Displaying the result and informing the user that
        // the expensive calculation finished
        setNumb(array);
        setCalc(false)

    }

    return(
        <div className = 'App'>
            <button onClick = {generate}>{calc ? 'Calculating...' : 'Calculate'}</button>
            {numb.map((number, index) => <div key = {index}>{number}</div>)}
        </div>
    );

}

export default App;

When a user clicks on the button, React will block the rendering until the generate() function finishes the execution. Thus, when the user clicks on the button, React won't update the value of the button to Calculating....

You need to give React some time to update de DOM before performing the expensive calculation. The easiest way to do it is to delay the expensive calculation a few ms using a sleep() function.

Example:

jsx
import React, { useState } from 'react';

const App = () => {

    const [calc, setCalc] = useState(false);
    const [numb, setNumb] = useState([]);

    const generate = async () => {

        // Informing the user that we are calculating the serie
        setCalc(true);

        // Delaying the expensive calculation 50 ms
        // JavaScript equivalent to sleep(ms)
        await new Promise(r => setTimeout(r, 50));

        // Start of expensive calculation
        // React will be blocked at this point
        let array = Array(2**15).fill(0).map(num => Math.random());

        // Displaying the result and informing the user that
        // the expensive calculation finished
        setNumb(array);
        setCalc(false)

    }

    return(
        <div className = 'App'>
            <button onClick = {generate}>{calc ? 'Calculating...' : 'Calculate'}</button>
            {numb.map((number, index) => <div key = {index}>{number}</div>)}
        </div>
    );

}

export default App;

Now the user will see Calculating... on the button after clicking on it.

Hi, I'm Erik, an engineer from Barcelona. If you like the post or have any comments, say hi.