Regardless of all the optimizations we do in in React apps, rendering process so far has been a blocking and cannot be interrupted. With the introduction of new experimental React Concurrent Mode, rendering becomes interruptible.
Previous workarounds
Debounce and throttle are common workarounds to mitigate for example stutter in an input field while typing.
Debounce – Only update the list after the user stops typing Throttle – Update the list with a certain maximum frequency
Similar to git branches
Concurrent Mode is like React working on git branches.
Eg:- Navigating between two screens in an app
In React Concurrent Mode, we can tell React to keep showing the old screen(like another git branch), fully interactive, with an inline loading indicator. And when the new screen is ready, React can take us to it.
Concurrency
CPU
For CPU-bound updates
like creating DOM nodes and running component code, concurrency means that a more urgent update can “interrupt” rendering that has already started.
For IO-bound updates
like fetching code or data from the network, concurrency means that React can start rendering in memory even before all the data arrives, and skip showing jarring empty loading states.
Install
Install the experimental version of react and react-dom.
yarn add react@experimental react-dom@experimental
Run your app, run your build, run your tests/type checking and make sure there are no issues.
Likely issues:
- String Refs, Legacy Context, or findDOMNode
- All the lifecycle methods that have the
unsafe_
prefix
Enable Concurrent Mode:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app';
const rootElement = document.getElementById('root');
// ReactDOM.render(<App />, rootEl)
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);
Try Concurrent Mode
Concurrent Mode enables two new features:
- Time slicing
- Suspense for everything asynchronous
Let’s create an app for testing out these features with create-react-app.
Experimental
The API for suspense is likely to change before suspense is officially released, so this is still in experimental stage.
Write a function for creating an actor when an actor name is provided in the input field:
function createActor(name) {
let status = 'pending';
let result;
let suspender = getActor(name).then(
name => {
status = 'success';
result = name;
},
error => {
status = 'error';
result = error;
}
);
return {
read() {
if (status === 'pending') throw suspender;
if (status === 'error') throw result;
if (status === 'success') return result;
}
};
}
Let’s create a function to imitate an HTTP request:
function getActor(name) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(`Your favorite actor is ${name}!`);
// Uncomment below to test rejecting instead
// reject(new Error(`Oops! could not load message for ${name}`))
}, 1000);
});
}
Now we can create a form so we can use the above functions
function Form() {
const [actor, setActor] = useState(null);
const [startTransition, isPending] = useTransition({
timeoutMs: 3000
});
function handleSubmit(event) {
event.preventDefault();
const name = event.target.elements.nameInput.value;
startTransition(() => {
setActor(createActor(name));
});
}
return (
<div>
<form onSubmit={handleSubmit}>
<label htmlFor="nameInput">Name</label>
<input id="nameInput" />
<button type="submit">Submit</button>
</form>
<Suspense fallback={<p>Loading...</p>}>
<Message actor={actor} isPending={isPending} />
</Suspense>
</div>
);
}
function Message({ actor, isPending }) {
return (
<p style={{ opacity: isPending ? 0.4 : 1 }}>
{actor ? actor.read() : 'Please provide an actor'}
</p>
);
}
Here is the full example of React concurrent mode in action: