⚡ TECH BLOG
Home
Blog
Tags
About
⚡

Powered by Next.js 15 & Modern Web Tech ⚡

Back to Home

React 18: New Features and Upgrade Guide

March 15, 2022
reactjavascriptfrontendwebdev
React 18: New Features and Upgrade Guide

React 18: New Features and Upgrade Guide

React 18 introduces concurrent features that fundamentally improve how React applications render. Let's explore the new capabilities.

Installation

npm install react react-dom

Automatic Batching

React 18 automatically batches state updates, even in promises and timeouts.

Before React 18

// Only batched in React event handlers
function handleClick() {
  setCount(c => c + 1);
  setFlag(f => !f);
  // Only one re-render
}

// NOT batched before React 18
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // Two re-renders
}, 0);

With React 18

// Now batched everywhere!
setTimeout(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // Only one re-render!
}, 0);

fetch('/api').then(() => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // Only one re-render!
});

Transitions

Transitions let you mark UI updates as non-urgent.

useTransition Hook

import { useTransition } from 'react';

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

  const handleChange = (e) => {
    // Urgent: update input immediately
    setQuery(e.target.value);
    
    // Non-urgent: can be delayed
    startTransition(() => {
      setResults(filterResults(e.target.value));
    });
  };

  return (
    <div>
      <input value={query} onChange={handleChange} />
      {isPending && <span>Loading...</span>}
      <Results data={results} />
    </div>
  );
}

startTransition API

import { startTransition } from 'react';

// Mark updates as transitions
startTransition(() => {
  setPage(nextPage);
});

Suspense for Data Fetching

Basic Suspense

import { Suspense } from 'react';

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <DataComponent />
    </Suspense>
  );
}

function DataComponent() {
  const data = use(fetchData()); // Suspends until data is ready
  return <div>{data}</div>;
}

Suspense with Transitions

function App() {
  const [page, setPage] = useState('home');
  const [isPending, startTransition] = useTransition();

  const navigate = (newPage) => {
    startTransition(() => {
      setPage(newPage);
    });
  };

  return (
    <div>
      <nav>
        <button onClick={() => navigate('home')}>Home</button>
        <button onClick={() => navigate('about')}>About</button>
      </nav>
      
      {isPending && <LoadingIndicator />}
      
      <Suspense fallback={<PageLoading />}>
        <Page name={page} />
      </Suspense>
    </div>
  );
}

New Hooks

useId

Generate unique IDs for accessibility:

import { useId } from 'react';

function Checkbox() {
  const id = useId();
  
  return (
    <>
      <label htmlFor={id}>Checkbox</label>
      <input id={id} type="checkbox" />
    </>
  );
}

useDeferredValue

Defer updating a value:

import { useDeferredValue, useMemo } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  
  const results = useMemo(() => 
    filterLargeList(deferredQuery),
    [deferredQuery]
  );
  
  return <Results data={results} />;
}

useSyncExternalStore

Subscribe to external stores:

import { useSyncExternalStore } from 'react';

function useOnlineStatus() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('online', callback);
      window.addEventListener('offline', callback);
      return () => {
        window.removeEventListener('online', callback);
        window.removeEventListener('offline', callback);
      };
    },
    () => navigator.onLine,
    () => true // Server snapshot
  );
}

useInsertionEffect

For CSS-in-JS libraries:

import { useInsertionEffect } from 'react';

function useStyle(rule) {
  useInsertionEffect(() => {
    const style = document.createElement('style');
    style.textContent = rule;
    document.head.appendChild(style);
    return () => style.remove();
  }, [rule]);
}

Strict Mode Changes

React 18's Strict Mode is stricter:

<React.StrictMode>
  <App />
</React.StrictMode>

Double Invocation

Components are mounted, unmounted, and remounted:

function Component() {
  useEffect(() => {
    console.log('Mount');
    return () => console.log('Unmount');
  }, []);
  
  return <div>Component</div>;
}

// Console output in Strict Mode:
// Mount
// Unmount
// Mount

Concurrent Rendering

Key Concepts

  1. Rendering is interruptible - React can pause and resume
  2. Updates have priority - Urgent updates interrupt non-urgent
  3. Background rendering - Prepare new content while showing old

Example: Priority Updates

function App() {
  const [text, setText] = useState('');
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    const newText = e.target.value;
    
    // High priority: immediate
    setText(newText);
    
    // Low priority: can be delayed
    startTransition(() => {
      renderExpensiveContent(newText);
    });
  };

  return (
    <div>
      <input value={text} onChange={handleChange} />
      <ExpensiveComponent text={text} />
    </div>
  );
}

Server-Side Rendering Improvements

Streaming SSR

// server.js
import { renderToPipeableStream } from 'react-dom/server';

const { pipe } = renderToPipeableStream(<App />, {
  bootstrapScripts: ['/client.js'],
  onShellReady() {
    response.setHeader('Content-Type', 'text/html');
    pipe(response);
  },
});

Selective Hydration

// Components hydrate as they become visible
<Suspense fallback={<Loading />}>
  <SlowComponent />
</Suspense>

Upgrade Guide

1. Update Dependencies

npm install react react-dom

2. Update Entry Point

// Before
import ReactDOM from 'react-dom';
ReactDOM.render(<App />, root);

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

3. Update Tests

// @testing-library/react v13+
import { render } from '@testing-library/react';

test('renders', () => {
  render(<App />);
});

Breaking Changes

  1. Automatic batching - May affect code expecting multiple renders
  2. Stricter Strict Mode - Effects run twice
  3. New root API - createRoot required for concurrent features

Opting Out of Batching

import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCount(c => c + 1);
  });
  // React has updated the DOM
  flushSync(() => {
    setFlag(f => !f);
  });
  // Two renders, immediate DOM updates
}

Conclusion

React 18's concurrent features enable better user experiences by prioritizing urgent updates and allowing React to prepare content in the background. Upgrade is straightforward, and most applications will benefit automatically from automatic batching.

Share:

💬 Comments