React 19 Updates: Complete Look at New Features with Examples

Here is the list of the all newly added features in the React 19:

  • React Compiler

  • Server Components

  • React 19 <form>

  • Server Actions

  • New Hooks: useActionState, useFormStatus, useOptimistic

  • New API: use

  • Existing Improvements: Ref as a Prop, ref callbacks, Context as a provider, initial value for useDeferredValue

  • Document Metadata Support, Stylesheet Support, Async Script Support

  • Preloading Resources

  • Custom Elements Support

  • Improved Hydration Error Reporting

React Compiler:

The React Compiler is a tool that converts your React code into simpler and faster JavaScript. This makes your app run more efficiently.

Key Points about the React Compiler:

  1. Not Built into React 19:
    The React Compiler is not included in React 19. It is a separate library, and React 19 does not make any build changes for you. This means the React Compiler is optional, and you can even use it with React 18.

  2. No Need for Manual Memoization:
    Usually, when building React apps, you might use APIs like useMemo, useCallback, or React.memo to optimize performance. The React Compiler can handle this for you automatically. You don’t have to write extra code for these optimizations.

  3. Smart Optimizations:
    The compiler understands JavaScript and React’s rules. It uses this knowledge to improve your components' performance automatically.

  4. Skips Optimization When Necessary:
    If your code breaks React’s rules as present in official docs, the compiler will skip optimizing those parts. This prevents any issues in your app.

  5. Opting Out of the Compiler:
    If needed, you can exclude specific components from being optimized by the compiler. This gives you control over how your app is processed.

Important Reminder:

The React Compiler is powerful, but with great power comes great responsibility. You’ll need to use it carefully to ensure your app works as expected.

Example:

function ProductPage({ products, heading }) {
  const featuredProducts = getFeatured(products);
  const totalProducts = products.length;

  return (
    <>
      <Heading heading={heading} totalProducts={totalProducts} />
      <ProductList products={products} />
      <FeaturedProducts featuredProducts={featuredProducts} />
    </>
  );
}

function Heading({ heading, totalProducts }) {
  return (
    <header>
      <h1>{heading}</h1>
      <p>Total Products: {totalProducts}</p>
    </header>
  );
}

function ProductList({ products }) {
  return (
    <ul>
      {products.map((product, index) => (
        <li key={index}>{product}</li>
      ))}
    </ul>
  );
}

function FeaturedProducts({ featuredProducts }) {
  return (
    <div>
      <h2>Featured Products</h2>
      <ul>
        {featuredProducts.map((product, index) => (
          <li key={index}>{product}</li>
        ))}
      </ul>
    </div>
  );
}

Let us look at the above code:

  • In ProductPage, values like featuredProducts (getFeatured(products)) and totalProducts (products.length) are recalculated on every render.

  • Even if the products array hasn't changed, new values are passed as props to the children (Heading and FeaturedProducts).

This causes Heading and FeaturedProducts to re-render, even when the actual data is unchanged.

Child components (Heading, ProductList, FeaturedProducts) are not wrapped in React.memo.

Even if their props don’t change, they still re-render whenever the parent (ProductPage) re-renders because React doesn’t know whether the props are identical.

That means we need to do many things to make our component optimized like the code below:

import React, { useMemo } from "react";

function ProductPage({ products, heading }) {
  const featuredProducts = useMemo(() => getFeatured(products), [products]);
  const totalProducts = useMemo(() => products.length, [products]);

  return (
    <>
      <Heading heading={heading} totalProducts={totalProducts} />
      <ProductList products={products} />
      <FeaturedProducts featuredProducts={featuredProducts} />
    </>
  );
}

const Heading = React.memo(function Heading({ heading, totalProducts }) {
  console.log("Heading rendered");
  return (
    <header>
      <h1>{heading}</h1>
      <p>Total Products: {totalProducts}</p>
    </header>
  );
});

const ProductList = React.memo(function ProductList({ products }) {
  console.log("ProductList rendered");
  return (
    <ul>
      {products.map((product, index) => (
        <li key={index}>{product}</li>
      ))}
    </ul>
  );
});

const FeaturedProducts = React.memo(function FeaturedProducts({ featuredProducts }) {
  console.log("FeaturedProducts rendered");
  return (
    <div>
      <h2>Featured Products</h2>
      <ul>
        {featuredProducts.map((product, index) => (
          <li key={index}>{product}</li>
        ))}
      </ul>
    </div>
  );
});

In the optimized code above, we apply a lot of memorization to our components to make them efficient. However, all of this can be easily handled by the React compiler, so you don't need to add any memoization manually.

Server Components:

React Server Components are a feature in React that allows components to run on the server instead of the browser. The server processes these components and sends the resulting HTML to the client. Server Components are stateless and do not have React hooks like useState or useEffect.

Problems in Traditional React Client-Side Rendering (CSR)

  1. Layout Shift Problem:
    When fetching data on the client side, some parts of the UI load before the data arrives, causing visible "jumps" on the page as elements adjust. This affects the user experience.

  2. Network and Component Rendering Waterfall Problem:
    In CSR, fetching data and rendering components happen sequentially. One component's data request may delay another, causing slower rendering.

  3. Performance Issues from Large Bundles:
    In CSR, the browser downloads a large JavaScript bundle, which slows down the page load and initial rendering.

How React Server Components (RSCs) Solve These Problems

  1. Faster Data Fetching on the Server:
    Since RSCs run on the server, they can fetch data faster using server-side APIs and databases without relying on the user's browser.

  2. Eliminating Client-Side JavaScript for Server-Rendered Parts:
    Parts of the UI rendered on the server do not require JavaScript on the client, reducing the bundle size.

  3. Improved Page Loads and Initial Rendering:
    The server sends pre-rendered components to the client, ensuring the page is usable quickly.

  4. Better Search Engine Indexing:
    Server-rendered HTML is fully available when the page loads, making it easier for search engines to index the content.

  5. Secure Sensitive Data:
    RSCs allow you to keep sensitive logic and data on the server, preventing exposure to the client.

Example Code: RSC vs. CSR:

Traditional Client-Side Rendering (CSR):

import { useEffect, useState } from 'react';

function ClientComponent() {
  const [data, setData] = useState([]);

  useEffect(() => {
    // Fetching data on the client
    fetch('/api/data')
      .then((res) => res.json())
      .then((data) => setData(data));
  }, []);

  return (
    <div>
      <h1>Client-Side Rendered Data</h1>
      {data.length === 0 ? (
        <p>Loading...</p>
      ) : (
        <ul>
          {data.map((item) => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
}

React Server Components Example (RSCs):

import { fetchDataFromDatabase } from './db-service';

export default async function ServerComponent() {
  const data = await fetchDataFromDatabase(); // Fetching data directly on the server

  return (
    <div>
      <h1>Server-Side Rendered Data</h1>
      <ul>
        {data.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
}

// This component will render on the server and send HTML to the client.

Key Improvements with RSCs in the Example:

  1. No Layout Shift:
    The server pre-renders the ServerComponent with data, so users see the final layout immediately.

  2. No Waterfall:
    Data fetching and rendering happen simultaneously on the server, avoiding delays caused by sequential requests in CSR.

  3. Reduced Bundle Size:
    ServerComponent sends pre-rendered HTML without client-side JavaScript logic, reducing the bundle size.

  4. Improved Performance:
    Users get a ready-to-use page faster, as the server handles the heavy lifting.

  5. Secure Logic:
    The database query (fetchDataFromDatabase) runs on the server, keeping sensitive information secure.

This demonstrates how RSCs can enhance performance, user experience, and security compared to traditional CSR.

React <form> action attribute:

In React 19, the action attribute of the <form> element works a bit differently than in regular HTML. Here’s a simple breakdown:

  • If you provide a URL to the action attribute, the form will work like a normal HTML form, and the browser will send the data to that URL.

  • If you provide a function to the action attribute, React will use that function to handle the form submission.

  • This function can be asynchronous and will receive the form data as an argument when the form is submitted.

  • You can also override the action attribute by adding a formAction attribute to buttons like <button>, <input type="submit">, or <input type="image">.

  • If you use a function in action or formAction, the form will always use the POST method to send the data, even if you specify a different method in the method attribute.

Server Functions:

Server Functions are special functions that can be run on the server when called from client-side components. They help protect sensitive data and perform secure operations since the actual work happens on the server.

What are server actions then?

Until September 2024, we referred to all Server Functions as “Server Actions”. If a Server Function is passed to an action prop or called from inside an action then it is a Server Action, but not all Server Functions are Server Actions A "Server Action" is a specific type of Server Function. It is designed to handle user interactions, like submitting a form. Server Actions typically use the POST method and provide a more structured way to update data and the user interface on the client side from the server.

How are Server Functions marked?

When a Server Function is defined with the "use server" directive, React will automatically create a reference to the server function and pass that reference to the Client Component. When that function is called on the client, React will send a request to the server to execute the function and return the result. Server Components can also define Server Functions with the "use server" directive.

How do they work?

When you call a Server Function from the client, React automatically sends a network request to the server to execute the function and return the results.

Marking an entire file?

Instead of marking each function individually with 'use server', you can mark the entire file. This means all functions in the file will be treated as server-side functions.

Server Actions and Forms?

React now supports using Server Actions in forms. This means you can securely handle form submissions on the server, making it easier to manage data updates and UI changes.

A common misunderstanding about 'use server'

Some people think that Server Components (components rendered on the server) are marked with 'use server'. That’s not true. The 'use server' directive is only for Server Functions and Server Actions, not for marking Server Components.

Example of Server Function:

// Server Component
import Button from './Button';

function EmptyNote() {
  async function handleCreateNote() {
    // Server Function
    'use server';

    try {
      await db.notes.create();
      console.log('Note created successfully');
    } catch (error) {
      console.error('Error creating note:', error);
    }
  }

  return <Button onClick={handleCreateNote} />;
}

// Client Component
"use client";

export default function Button({ onClick }) {
  return (
    <button onClick={onClick} type="button">
      Create Empty Note
    </button>
  );
}

Example of Server Actions:

async function subscribeNewsLetter(formData) {
  'use server';
  const username = formData.get('email');

  // Simulate saving the email to the subscribers table
  console.log(`Subscribing email: ${email}`);
}

export default function App() {
  return (
    <form action={subscribeNewsLetter}>
      <input type="text" name="email" />
      <button type="submit">Subscribe</button>
    </form>
  );
}

New Hooks: useActionState, useFormStatus, useOptimistic:

These are the three new hooks that have been introduced on React 19 for better handling the forms:

useActionState:

useActionState is a Hook that lets you update the state based on the result of a form action. The state reflects the value returned by the action when the form was last submitted. If the form hasn't been submitted yet, it uses the initial state you provide as the second parameter.

Please see the below example for more clearer understanding:

import { useActionState, useState } from "react";
import { addToCart } from "./actions.js";

function AddToCartForm({itemID, itemTitle}) {
  const [message, formAction, isPending] = useActionState(addToCart, null);

  return (
    <form action={formAction}>
      <h2>{itemTitle}</h2>

      <input type="hidden" name="itemID" value={itemID} />
      <button type="submit">Add to Cart</button>

      {isPending ? "Loading..." : message}
    </form>
  );
}

export default function App() {
  return (
    <>
      <AddToCartForm itemID="1" itemTitle="JavaScript: The Definitive Guide" />
      <AddToCartForm itemID="2" itemTitle="JavaScript: The Good Parts" />
    </>
  )
}

useFormStatus:

A hook that gives you status information of the last form submission. We can display a pending state during form submission, we can read the form data being submitted. It only works when it's used in a component that is part of a <form> tag. It will only return status information for a parent <form>

Please see the below example for more clearer understanding:

// UsernameForm.js

import {useState, useMemo, useRef} from 'react';
import {useFormStatus} from 'react-dom';

export default function UsernameForm() {
  const {pending, data} = useFormStatus();

  return (
    <div>
      <h3>Request a Username: </h3>

      <input type="text" name="username" disabled={pending}/>

      <button type="submit" disabled={pending}>
        Submit
      </button>

      <br />

      <p>{data ? `Requesting ${data?.get("username")}...`: ''}</p>
    </div>
  );
}

// App.js

import UsernameForm from './UsernameForm';
import { submitForm } from "./actions.js";
import { useRef } from 'react';

export default function App() {
  const ref = useRef(null);
  return (
    <form ref={ref} action={async (formData) => {
      await submitForm(formData);
      ref.current.reset();
    }}>
      <UsernameForm />
    </form>
  );
}

useOptimistic:

useOptimistic is a React Hook that allows you to show an immediate, temporary state while an async action (like a network request) is in progress. It returns an "optimistic" state based on the current state and input, allowing users to see a result right away, even though the action is still pending.

Please see the below example for more clearer understanding:

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

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(todos, (todos, newTodo) => [...todos, newTodo]);

  const handleAddTodo = async (newTodo) => {
    addOptimisticTodo(newTodo);

    try {
      // Simulate an API call
      await fakeApiCallToAddTodo(newTodo);
    } catch (error) {
      // Revert optimistic update if API call fails
      setTodos((prevTodos) => prevTodos.filter((todo) => todo !== newTodo));
    }
  };

  return (
    <div>
      <ul>
        {optimisticTodos.map((todo, index) => (
          <li key={index}>{todo}</li>
        ))}
      </ul>
      <button onClick={() => handleAddTodo(`New Todo ${Date.now()}`)}>Add Todo</button>
    </div>
  );
}

New API: use

The use API in React allows you to access or read the value of a resource, such as a Promise, and context.

Flexibility of the use API

Unlike React Hooks (such as useState, useEffect, etc.), which have strict rules about where they can be called, the use API can be invoked inside loops, conditionals, or other dynamic constructs.

Requirements for Using the use API

Any function that invokes use must be a React component or a custom hook. This ensures that the resource retrieval and management align with React's lifecycle and rendering behavior.

Data Fetching in Server Components

When fetching data in a Server Component, prefer async and await over use. async and await pick-up rendering from the point where await was invoked, whereas use re-renders the component after the data is resolved.

Creating and Passing Promises

Prefer creating Promises in Server Components and passing them to Client Components over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders.

Handling Resolved Promises

When passing a resolved Promise from a Server Component to a Client Component, the data it resolves to must be serializable. React requires that data shared between the server and client (like through server-side rendering or hydration) can be converted into a format that can be safely transferred and rebuilt. Non-serializable values, such as functions, circular references, or complex objects, will not transfer correctly and will cause errors. Therefore, only primitive data types, arrays, and plain objects should be used as the resolved value of a Promise.

Enhancing User Experience with Suspense and Error Boundaries

To make use work better and improve user experience, it's a good idea to use React Suspense and Error Boundaries. Suspense lets components "wait" for data to load, showing a loading state while waiting for promises to finish. Error Boundaries catch and handle errors during rendering, stopping crashes, and showing a backup UI if something goes wrong, which is important for handling tasks like data fetching.

Passing Promises Between Components

A Promise can be passed from a Server Component to a Client Component and resolved in the Client Component using the use API. Alternatively, you can resolve the Promise in a Server Component with await and pass the necessary data to the Client Component as a prop.

Avoiding Rendering Delays

However, using await in a Server Component will delay its rendering until the await statement is complete. Passing a Promise from a Server Component to a Client Component avoids blocking the rendering of the Server Component.

Example of using context with the Help of ‘use‘ API:

function Button({ show, children }) {
  if (show) {
    const theme = use(ThemeContext);
    const className = 'button-' + theme;

    return (
      <button className={className}>
        {children}
      </button>
    );
  }

  return false
}

Example of ‘use‘ API for data fetching:

import { use } from 'react'
import { fetchData } from './data.js'

export default function SearchResults({ query }) {
  if (query === '') {
    return null
  }

  const albums = use(fetchData(`/search?q=${query}`))

  if (albums.length === 0) {
    return (
      <p>
        No matches for <i>"{query}"</i>
      </p>
    )
  }

  return (
    <ul>
      {albums.map((album) => (
        <li key={album.id}>
          {album.title} ({album.year})
        </li>
      ))}
    </ul>
  )
}

Example of ‘use‘ API for data fetching with Suspense and Error boundaries:

import { use, Suspense } from "react";
import { fetchData } from './data.js'

function Message() {
  const messageContent = use(fetchData('/message'));
  return <p>Here is the message: {messageContent}</p>;
}

export function MessageContainer() {
  return (
    <ErrorBoundary>
        <Suspense fallback={<p>⌛Downloading message...</p>}>
            <Message />
        </Suspense>
    </ErrorBoundary>
  );
}

Example of ‘use‘ API for data fetching with the help of promises:

// Server Component
import db from './database';

async function Page({ id }) {
  // Will suspend the Server Component.
  const note = await db.notes.get(id);

  // NOTE: not awaited, will start here and await on the client. 
  const commentsPromise = db.comments.get(note.id);

  return (
    <div>
      {note}
      <Suspense fallback={<p>Loading Comments...</p>}>
        <Comments commentsPromise={commentsPromise} />
      </Suspense>
    </div>
  );
}


// Client Component
"use client";

import { use } from 'react';

function Comments({commentsPromise}) {
  // NOTE: this will resume the promise from the server.
  // It will suspend until the data is available.
  const comments = use(commentsPromise);
  return comments.map(commment => <p>{comment}</p>);
}

In the example above, the note content is crucial for the page to display, so we await it on the server. The comments are less important and appear below the fold, so we start the promise on the server and wait for it on the client using the use API. This will pause on the client side without stopping the note content from rendering.

Some Existing Improvements:

Some improvements have been made to the existing features of React, including enhanced ref handling, context, and deferred value usage.

Ref as a Prop

  • React 19 simplifies how refs are passed as props.

  • Key Improvement: There's no need to use forwardRef anymore. React will handle refs automatically when passed as a prop to components.

  • Ease of Transition: React will provide a codemod (a tool to automate code updates) to help developers convert existing code using forwardRef to this new approach seamlessly.

function CustomInput({ placeholder, ref }) {
  return <input placeholder={placeholder} ref={ref} />;
}

// ...

<CustomInput ref={ref} />;

Ref Callbacks for Cleanup

  • React 19 introduces a more flexible way to handle ref cleanups.

  • How It Works: Refs can now return a callback function that React will call during the component’s unmount phase.

function MeasureExample() {
  const [height, setHeight] = React.useState(0)

  const measuredRef = (node) => {
    const observer = new ResizeObserver(([entry]) => {
      setHeight(entry.contentRect.height)
    })

    observer.observe(node)

    return () => {
      observer.disconnect()
    }
  }

  return (
    <>
      <h1 ref={measuredRef}>Hello, world</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  )
}

Context as a Provider

  • Simplifying the usage of React Context.

  • Key Improvement: There's no longer a need to use <Context.Provider> explicitly. You can now use the <Context> component directly to provide values to consumers.

  • React will provide a codemod to convert existing providers.

// Example of React 18
<MyContext.Provider value={value}>
  <ChildComponent />
</MyContext.Provider>


// Example of React 19
<MyContext value={value}>
  <ChildComponent />
</MyContext>

useDeferredValue with Initial Value

  • React 19 adds more flexibility to useDeferredValue by introducing an initialValue option.

  • How It Works:

    • On the initial render, React will use the initialValue provided.

    • A re-render is scheduled in the background, during which the deferred value will replace the initialValue.

function Search({ deferredValue }) {
  // On initial render the value is ''.
  // Then a re-render is scheduled with the deferredValue.
  const value = useDeferredValue(deferredValue, "");

  return <Results value={value} />;
}

Document Metadata Support:

In previous versions of React, you needed third-party libraries (like react-helmet) to manage tags like: <title> <link> and <meta>. React 19 can handle these tags natively, even from nested components.

  • This means you don’t need external libraries anymore.

  • React will automatically hoist these tags to the <head> of the document.

Example of Document Metadata Support:

function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <title>{post.title}</title>
      <meta name="author" content="Jane Doe" />
      <link rel="author" href="https://x.com/janedoe" />
      <meta name="keywords" content={post.keywords} />
      <p>...</p>
    </article>
  );
}

Stylesheet Support:

React 19 introduces a feature to control the loading order of stylesheets using precedence. This enhancement simplifies colocating stylesheets with components, ensuring they are loaded only when needed.

Key considerations include:

  • If a component is rendered in multiple locations within your app, React deduplicates the associated stylesheet, ensuring it appears only once in the document.

  • For server-side rendering, React includes the stylesheet in the <head> tag. This guarantees the browser will delay painting until the stylesheet is fully loaded.

  • If a stylesheet is identified after streaming has begun, React ensures it is added to the <head> on the client side before revealing any dependent content through a Suspense boundary.

  • During client-side rendering, React waits for the newly rendered stylesheets to load before completing the render process.

function ComponentOne() {
  return (
    <Suspense fallback="loading...">
      <link rel="stylesheet" href="one" precedence="default" />
      <link rel="stylesheet" href="two" precedence="high" />
      <article>...</article>
    </Suspense>
  );
}

function ComponentTwo() {
  return (
    <div>
      <p>...</p>
      {/* Stylesheet "three" below will be inserted between "one" and "two" */}
      <link rel="stylesheet" href="three" precedence="default" />
    </div>
  );
}

Async scripts support:

Defer: Downloads the script while parsing the HTML. It runs the script after the HTML is fully parsed. This is the default option.

Async: Downloads the script while parsing the HTML and runs it as soon as it's downloaded. This can be risky because, even though it runs in parallel, it can slow down parsing if the script is heavy. Use this for scripts that don't rely on the DOM and aren't resource-intensive.

We can render async scripts in any component, which makes it easier to place scripts near the components they relate to. React only loads them if they are needed.

Here are a few things to remember:

  • If you render the same component in different places in your app, React will ensure the script is included only once in the document.

  • During server-side rendering, async scripts are added to the head and prioritized after more important resources that affect page rendering, like stylesheets, fonts, and image preloads.

function Component() {
  return (
    <div>
      <script async={true} src="..." />
      // ...
    </div>
  );
}

function App() {

  return (
    <html>
      <body>
        <Component>
          // ...
        </Component> // Won't duplicate script in the DOM
      </body>
    </html>
  );
}

Preloading Resources:

React 19 introduces several new APIs to enhance page load performance and user experience by loading and preloading resources like scripts, stylesheets, and fonts.

  • prefetchDNS

    When your app needs to connect to an external domain (like https://example.com) but hasn't connected yet, it can use prefetchDNS to resolve the domain's IP address ahead of time. This ensures that when the connection is required, the DNS lookup is already completed, saving time.

  • preconnect

    If your app knows it will communicate with a specific server (like an API or a CDN), it can use preconnect to set up a network connection (such as a TCP handshake and TLS negotiation) to that server early, even before requesting any resources.

  • preload

    When your app knows it will need a specific resource (like a font file, an image, or a script), it can use preload to fetch that resource in advance, ensuring it's ready when needed.

  • preloadModule

    This is similar to preload but specifically for JavaScript modules (ESM). If your app will later use a JavaScript module, preloadModule allows you to fetch the module early, ensuring it’s ready to execute when required.

  • preinit

    When your app needs to not only fetch but also prepare a script or stylesheet, preinit helps. It ensures the resource is both downloaded and evaluated (for scripts) or inserted into the document (for stylesheets) early, so it’s ready to use immediately.

    Scripts or modules often contain initialization logic or dependencies that need to be ready before the application can use them. By evaluating them early, you avoid delays caused by parsing or executing them at the last minute.

  • preinitModule

    This works like preinit but is specifically for JavaScript modules (ESM). It fetches and evaluates the module in advance, so it's ready for execution. If your app uses a module to generate interactive charts, preinitModule ensures the module is downloaded and evaluated before the user opens the analytics section. This way, charts are rendered immediately without delay.

// React code
import { prefetchDNS, preconnect, preload, preinit } from "react-dom";

function MyComponent() {
  preinit("https://.../path/to/some/script.js", { as: "script" });
  preload("https://.../path/to/some/font.woff", { as: "font" });
  preload("https://.../path/to/some/stylesheet.css", { as: "style" });
  prefetchDNS("https://...");
  preconnect("https://...");
}

// Resulting HTML
<html>
  <head>
    <link rel="prefetch-dns" href="https://..." />
    <link rel="preconnect" href="https://..." />
    <link rel="preload" as="font" href="https://.../path/to/some/font.woff" />
    <link
      rel="preload"
      as="style"
      href="https://.../path/to/some/stylesheet.css"
    />
    <script async="" src="https://.../path/to/some/script.js"></script>
  </head>
  <body>
    <!-- ... -->
  </body>
</html>

Custom Elements Support and Better Error Reporting:

Custom Elements let developers create their own HTML elements as part of the Web Components specification.

In earlier versions of React, using Custom Elements was challenging because React treated unrecognized props as attributes instead of properties.

React 19 fully supports Custom Elements and passes all tests on Custom Elements Everywhere.

Custom Elements Everywhere is a project that checks how well popular JavaScript frameworks and libraries support Custom Elements.

Previously, React would show the error twice: once for the original error and again after failing to recover, along with error details.

In React 19, error handling is improved by removing duplicate error messages.

Improved Hydration Error Reporting

Hydration errors are improved by logging a single mismatch error instead of multiple ones. Error messages now include suggestions on how to fix the error.

Hydration errors with third-party scripts and browser extensions are also better handled. Previously, elements added by these scripts or extensions would cause mismatch errors.

In React 19, unexpected tags in the head and body are ignored and do not cause errors.

React 19 introduces two new root options, in addition to the existing onRecoverableError, to help clarify why an error occurs:

  • onCaughtError triggers when React catches an error in an Error Boundary.

  • onUncaughtError triggers when an error is thrown and not caught by an Error Boundary.

  • onRecoverableError triggers when an error is thrown and automatically recovered.