The Biggest React Update in Years

React 18 represents the most significant architectural change to React since the introduction of hooks in version 16.8. At its core is concurrent rendering, a new behind-the-scenes mechanism that allows React to prepare multiple versions of your UI simultaneously. This is not a feature you use directly but rather a foundation that enables a new class of features designed to make your applications feel faster and more responsive.

At StrikingWeb, we have been testing React 18's release candidates across several client projects, and the improvements are tangible. Applications that previously felt sluggish during complex state updates now remain responsive. Here is what changed and what it means for your projects.

Understanding Concurrent Rendering

In React 17 and earlier, rendering is synchronous. When React starts rendering an update, nothing can interrupt it until it finishes drawing to the screen. For simple updates, this is fine. But for complex updates, like filtering a large list or rendering a heavy chart, the UI can freeze while React works through the rendering process.

Concurrent rendering changes this fundamental behavior. React can now start rendering an update, pause in the middle to handle a more urgent update (like a user typing in a search field), and then resume the original rendering. The key insight is that React can prepare new UI in the background without blocking the main thread.

This does not happen automatically for your existing code. Concurrent features are opt-in, and you activate them by using the new concurrent APIs like useTransition, useDeferredValue, and Suspense.

Key Features in React 18

Automatic Batching

React has always batched state updates that happen inside React event handlers. If you call setState three times in a click handler, React renders once, not three times. However, updates inside promises, timeouts, and native event handlers were not batched in React 17.

React 18 extends automatic batching to all updates, regardless of where they originate. This means fewer re-renders and better performance without any code changes. Here is the practical impact:

// React 17: Two re-renders
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React renders twice
}, 1000);

// React 18: One re-render
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React batches and renders once
}, 1000);

useTransition

The useTransition hook is perhaps the most practically useful new feature. It allows you to mark certain state updates as "transitions," telling React that these updates are not urgent and can be interrupted by more pressing updates.

The classic example is a search input that filters a large list. Without transitions, every keystroke triggers a re-render of the entire list, making the input feel laggy. With useTransition, the input update is treated as urgent while the list filtering is treated as a transition that can be deferred.

import { useTransition, useState } from 'react';

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleChange(e) {
    // Urgent: update the input value immediately
    setQuery(e.target.value);

    // Non-urgent: filter results can wait
    startTransition(() => {
      setResults(filterLargeList(e.target.value));
    });
  }

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending ? <Spinner /> : <ResultsList results={results} />}
    </>
  );
}

useDeferredValue

useDeferredValue is the complement to useTransition. While useTransition wraps the state update itself, useDeferredValue wraps the value produced by a state update. It tells React to keep showing the old value while a new value is being calculated in the background.

This is particularly useful when you receive a value as a prop and want to defer the expensive rendering it triggers, without control over the state update that produced it.

Suspense for Data Fetching

Suspense has existed since React 16 for code splitting, but React 18 expands its role to data fetching scenarios. When combined with concurrent rendering, Suspense allows you to declaratively specify loading states for components that depend on asynchronous data.

The Suspense model simplifies the loading state logic that typically clutters React components. Instead of checking isLoading flags in every component, you wrap data-dependent components in Suspense boundaries and provide fallback UI that shows while data loads.

Server-Side Rendering Improvements

React 18 introduces streaming server-side rendering with selective hydration. Instead of rendering the entire page on the server before sending anything to the client, React 18 can stream HTML as it is generated and selectively hydrate components on the client as they become available.

This means users see content faster, and interactive elements become responsive sooner. For pages with a mix of fast and slow data sources, this is a significant improvement. The header, navigation, and cached content can be interactive immediately while slower sections like personalized recommendations load in the background.

Upgrading to React 18

The React team designed React 18 with a gradual upgrade path. Your existing code will work without changes when you upgrade. Concurrent features are opt-in, and you activate them by changing your root rendering call.

// React 17
import { render } from 'react-dom';
render(<App />, document.getElementById('root'));

// React 18
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);

Simply switching to createRoot enables automatic batching for all updates. To use concurrent features like useTransition and Suspense for data fetching, you need to update specific components that would benefit from them.

What to Watch Out For

While the upgrade path is smooth, there are areas that require attention.

React 18 is not about learning new syntax. It is about React getting smarter about when and how it renders, so your applications stay responsive even as they grow more complex.

Our Recommendation

For new projects, we are building with React 18 from day one at StrikingWeb. The automatic batching alone provides performance benefits with zero effort. For existing projects, we recommend upgrading to React 18 with createRoot but adopting concurrent features gradually, starting with the components where users notice the most lag.

If you are planning a React upgrade or starting a new project and want to take advantage of these concurrent features, our frontend team can help you architect your application for the best possible user experience.

Share: