TL;DR: React 19 revolutionizes state management and data handling with async functions and the new useTransition hook. Explore the introduction of Actions for streamlined data mutations and improved UI interactions.
This blog delves into React 19, a significant milestone for front-end developers. The beta version of React 19 was released on April 25, 2024, bringing a host of new features, improvements, and changes. We’ll explore how these updates impact developers, highlight the key enhancements, and compare React 19 to its predecessors.
Let’s dive into the new features in React 19, focusing on the use of async functions and how they enhance data handling and state management.
Async functions and state management with code examples
Traditional approach (before React 19)
Previously, handling data mutations and updating the state after asynchronous operations involved several steps:
- Make the async request (e.g., fetch data).
- Set a loading state (optional).
- Handle the response:
- Success: Update state with the retrieved data.
- Error: Update state with error information and display an error message.
- Clear the loading state (optional).
This required manual code for each step, making state management cumbersome. Here’s a demonstration of the old way:
import { useState, useEffect } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true); // Set loading state to true
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
setData(data);
} catch (error) {
setError(error);
} finally {
setIsLoading(false); // Set loading state to false (even on error)
}
};
fetchData();
}, []); // Run effect only once on component mount
return (
<div>
{isLoading ? (
<p>Loading...</p>
) : error ? (
<p>Error: {error.message}</p>
) : data ? (
<p>Fetched Data: {JSON.stringify(data)}</p>
) : null}
<button disabled={isLoading} onClick={fetchData}>
Fetch Data
</button>
</div>
);
}
export default MyComponent;
In the previous code example, we manage multiple states, including data, isLoading (pending state), and error. When the component mounts, useEffect with an empty dependency array triggers a data fetch. The fetchData function inside useEffect is asynchronous. We set isLoading to true before initiating the request and to false after the request completes, including in the finally block to handle any errors. To prevent multiple requests, the button is disabled while isLoading is true. We handle success and error states by updating the respective variables accordingly.
Async functions and useTransition in React 19
React 19 introduces useTransition, a powerful hook that streamlines handling asynchronous operations within your components. You can now directly use async functions to perform data mutations and manage state updates more smoothly.
Here’s an example showcasing how useTransition simplifies handling the pending state during an asynchronous data fetch:
import { useState, useTransition } from 'react';
function MyComponent() {
const [data, setData] = useState(null);
const [isPending, startTransition] = useTransition();
const fetchData = async () => {
startTransition(async () => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
setData(data);
});
};
return (
<div>
<button onClick={fetchData} disabled={isPending}>
{isPending ? 'Loading...' : 'Fetch Data'}
</button>
{data && <p>Fetched Data: {JSON.stringify(data)}</p>}
</div>
);
}
export default MyComponent;
In the previous code example, we utilize useState to manage the data and isPending states. The useTransition hook provides isPending and startTransition. The fetchData function is asynchronous, and we wrap this async request within startTransition to indicate a state transition. During the data fetching process, isPending is set to true, which disables the button to prevent multiple requests. Once the data is successfully fetched, the data state is updated, and isPending is set to false.
Benefits of using useTransition
- Automatic pending state: useTransition manages the pending state automatically, improving code readability.
- Responsive UI: The button is disabled while fetching data, preventing multiple requests and maintaining a responsive UI.
React 19 Actions (new!)
Actions are a new concept in React 19 that encapsulates data mutation logic and side effects.
They offer several advantages:
- Pending state: Actions handle the pending state automatically.
- Optimistic updates: The useOptimistic hook facilitates optimistic updates by allowing you to temporarily update the UI with the expected response before the actual data arrives. If the request fails, the UI is reverted to its original state.
- Error handling: Actions provide built-in error handling. If a data mutation fails, you can display an error boundary to inform the user.
- Forms: When you pass a function to the action prop of a , React 19 uses an action by default. This action automatically handles the form submission process, including resetting the form after a successful submission.
React 19 introduces several features that simplify managing asynchronous operations and state updates. Here’s a breakdown of some key additions:
Optimistic updates with useOptimistic
Imagine a scenario where a user submits a form. Traditionally, you’d update the UI only after receiving confirmation from the server. However, with useOptimistic, you can provide immediate feedback by temporarily reflecting the expected change in the UI.
Here is an example:
import { useState, useOptimistic } from 'react';
function MyForm() {
const [name, setName] = useState('');
const [optimisticName, setOptimisticName] = useOptimistic(name);
const handleChange = (event) => {
setName(event.target.value);
setOptimisticName(event.target.value); // Update optimistic name
};
const handleSubmit = async (event) => {
event.preventDefault();
// Simulate async submission
await new Promise((resolve) => setTimeout(resolve, 1000));
// Update state after server confirmation (replace with actual logic)
setName(name);
};
return (
<form onSubmit={handleSubmit}>
<label>
Name:
<input type="text" value={optimisticName} onChange={handleChange} />
</label>
<button type="submit">Submit</button>
</form>
);
}
export default MyForm;
In the previous code example, the useOptimistic (name) function establishes an optimistic state variable that mirrors the anticipated modification. As the user inputs text, the handleChange function concurrently updates the actual name and the optimisticName. This allows the UI to display the optimisticName, offering immediate visual feedback to the user. Upon submitting the form, a simulated server interaction should be replaced with the actual logic specific to the application. Following this simulated confirmation, the true state, represented by name, is then updated to reflect the change.
Simplifying state management with useActionState
The useActionState hook is designed to simplify state management based on the result of a form action. The useActionState hook accepts a function, referred to as the Action, and returns a wrapped version of this Action. When the wrapped Action is called, useActionState will provide the last result of the Action as data and the pending state of the Action as pending.
Let’s say you have a form where users can submit their email to subscribe to a newsletter. Consider the following code:
const [message, sendEmailAction, isPending] = useActionState(
async (previousState, email) => {
try {
const result = await sendEmail(email);
return result; // Return success message
} catch (error) {
return error; // Return error message
}
},
null
);
In the previous code example, when the user submits the form, sendEmailAction is called with the email address. While the email is being sent, isPending is true, which can be used to disable the submit button to prevent multiple submissions. Once the email is sent, a message will contain either the success message or the error message, which can be displayed to the user. This example focuses on the main logic of useActionState, showing how it manages the state of an asynchronous action and handles both pending and result states. For more information, you can refer to the documentation for useActionState.
Streamlining form handling in React 19
React 19 introduces new <form> features in react-dom, allowing functions to be passed as the Action and formAction props of <form>, <input>, and <button> elements. This enables automatic form submission with Actions. When a <form> Action is successful, React will automatically reset the form for uncontrolled components. If you need to reset the form manually, you can use the new requestFormReset API in React DOM.
function LoginForm() {
const [error, submitAction, isPending] = useActionState(
async (previousState, formData) => {
try {
await loginUser(formData);
return null; // No error
} catch (error) {
return error; // Return error message
}
},
null
);
return (
<form action={submitAction}>
<input type="text" name="username" placeholder="Username" required />
<input type="password" name="password" placeholder="Password" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Logging in...' : 'Login'}
</button>
{error && <p>{error}</p>}
</form>
);
}
In the previous code example, the submitAction is called with form data when the form is submitted. submitAction is the wrapped function returned by useActionState. When you call submitAction with the form data, it triggers the loginUser function, managing the asynchronous login process. Refer to the react-dom docs for <form> and <input> for additional information.
Simplifying form state with useFormStatus
In design systems, it’s common to create components that need to access information about the <form> they are part of, without passing props down through multiple layers of components. While this can be achieved using Context, React 19 introduces a new hook called useFormStatus to simplify this process.
import { useFormStatus} from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>Submit</button>;
}
Suspense and data fetching with React’s use API
React 19 introduces a new API called use to read resources during render. This API allows you to handle promises directly within your components. React will suspend rendering until the promise is resolved. This is particularly useful for data fetching scenarios where you want to wait for the data to be available before rendering the component.
The following example fetches user data and displays it only when the data is available:
import { use, Suspense } from 'react';
function UserProfile({ userPromise }) {
// `use` will suspend until the promise resolves.
const user = use(userPromise);
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
function App({ userPromise }) {
// When `use` suspends in UserProfile,
// this Suspense boundary will be shown.
return (
<Suspense fallback={<div>Loading user data...</div>}>
<UserProfile userPromise={userPromise} />
</Suspense>
);
}
export default App;
In the previous code example, the UserProfile component uses the use function to read the userPromise. It suspends rendering until the promise resolves and then displays the user’s name and email.
React server components
React 19 introduces a novel feature known as server components, designed to render components in advance. This rendering can occur either during the build stage on a CI server or in real-time with each request via a web server. The prerendering process occurs in a distinct environment, separate from the client application or server-side rendering server. It is specifically designated as the server within React server components. These components can run once when building or dynamically with every incoming request.
With the incorporation of React server components, React 19 now supports libraries targeting it as a peer dependency. This is facilitated through the react-server export condition, catering to frameworks aligned with the full-stack React architecture.
Executing async functions with React server actions
Server actions enable client components to invoke asynchronous functions executed on the server.
When you define a server action using the use server directive, your framework will automatically generate a reference to the server function and pass this reference to the client component. When the function is called on the client side, React sends a request to the server to execute the function and returns the result. Server actions can be defined within server components and then passed as properties to client components. Alternatively, they can be imported directly into client components for use.
Improvements in React 19
Hydration error reporting improvements
React 19 enhances error reporting, particularly for hydration errors. These errors occur when there’s a mismatch between the server-rendered HTML and the JavaScript code trying to hydrate it on the client side. React 19 also has enhanced error reporting for hydration errors in react-dom. Previously, multiple errors were logged in the development environment without clear information about the mismatches, as shown in the following example:
Warning: Text content did not match. Server: “Server” Client: “Client”
at span
at App
Warning: An error occurred during hydration. The server HTML was replaced with client content in <div>
Now, a single message with a diff of the mismatch is logged:
Uncaught Error: Hydration failed because the server rendered HTML didn’t match the client. As a result, this tree will be regenerated on the client. This can happen if an SSR-ed Client Component is used:
- A server/client branch like `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()`.
- Date formatting in a user’s locale.
- External changing data without a snapshot.
- Invalid HTML tag nesting.
React 19 also enhanced error reporting to remove duplication and provide options for handling caught and uncaught errors.
Efficient ref handling via props
In React 19, you can now access ref as a prop for function components, eliminating the need for forwardRef
function MyInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// Usage
<MyInput ref={ref} />;
In React 19, forwardRef is no longer mandatory for new function components. A codemod tool is available to assist with updating your existing components to reflect this change. Future React versions plan to deprecate and eventually remove forwardRef entirely.
Simplified context provider usage
In React 19, you can now render <Context> directly as a provider, eliminating the need for Simplified context provider usage <Context.Provider>.
import React, { createContext } from 'react';
const ThemeContext = createContext('light');
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
export default App;
Ref cleanup functions
React 19 introduces support for returning cleanup functions from ref callbacks. When a component unmounts, React will invoke the cleanup function:
<input
ref={(ref) => {
// ref created
// NEW: return a cleanup function to reset
// the ref when the element is removed from the DOM.
return () => {
// ref cleanup
};
}}
/>
Previously, React called ref functions with null during unmounting. With cleanup functions, this step is skipped. Future versions will deprecate calling refs with null.
Additionally, TypeScript will now reject returning anything other than a cleanup function from a ref callback. To fix this, avoid implicit returns:
// Before
<div ref={current => (instance = current)} />
// After
<div ref={current => { instance = current }} />
Initial value for useDeferredValue
React 19 adds an initialValue option to useDeferredValue, allowing you to set a default value for the initial render:
function Search({ deferredValue }) {
// On initial render, the value is ''.
// Then a re-render is scheduled with the deferredValue.
const value = useDeferredValue(deferredValue, '');
return <Results query={value} />;
}
When initialValue is provided, useDeferredValue returns it for the initial render and schedules a background re-render with the deferredValue. This ensures a smoother initial rendering experience.
Support for async scripts
Previously, managing asynchronous scripts (those loaded after the initial page load) within React components could be cumbersome. React 19 introduces improved support for asynchronous scripts. You can now place async scripts anywhere in your component hierarchy without worrying about duplication or misplacement. React ensures each script is loaded and executed only once, even when referenced by multiple components.
Support for custom elements
Custom elements allow developers to create reusable web components using standard HTML, CSS, and JavaScript. React 19 integrates seamlessly with custom elements, so you can now leverage existing custom element libraries within your React apps without the need for conversion or additional wrappers.
Support for document metadata
Document metadata refers to information about the webpage that isn’t directly displayed but is useful for search engines and other tools. React 19 introduces the <DocumentHead> component, allowing you to easily manage document metadata within your React apps. This simplifies managing elements like <title>, <meta>, and <link> tags within your React components.
Enhanced stylesheet management
React 19 simplifies stylesheet handling by integrating support for stylesheets in both concurrent rendering on the client and streaming rendering on the server. By specifying the precedence of your stylesheets, React manages their insertion order and ensures external stylesheets load before displaying dependent content.
Example:
<link rel="stylesheet" href="foo" precedence="default" />
<link rel="stylesheet" href="bar" precedence="high" />
This approach helps maintain style precedence rules and ensures a smoother rendering process.
React 19’s new APIs for resource optimization
React 19 introduces several new APIs for loading and preloading browser resources, enhancing performance and user experience. Here are the key APIs:
- The preload API allows you to specify resources that should be loaded as soon as possible. This is useful for critical resources that are needed early in the page lifecycle.
- The preconnect API allows the browser to establish early connections to important third-party origins. This reduces latency by pre-establishing connections.
- The prefetchDNS allows you to specify the IP address of a DNS domain name that you expect to connect to.
These resource preloading APIs improve initial page loads by fetching fonts and other assets early, independent of stylesheets. They can also speed up client-side navigation by preloading resources for anticipated pages on user interaction. Refer to the resource preloading APIs documentation for more details.
Conclusion
React 19 offers a significant leap forward in the front-end developer experience with features that streamline data handling, state management, and overall app building. For additional insights and in-depth explanations, explore the official React 19 documentation alongside online tutorials and community forums.
We recommend reviewing the official React 19 upgrade guide for comprehensive instructions on managing the upgrade process. It details both breaking changes and noteworthy features.
The Syncfusion React UI components library is the only suite you will ever need to build an app. It contains over 85 high-performance, lightweight, modular, and responsive UI components in a single package.
The newest version of Essential Studio is available on the license and downloads page for existing Syncfusion customers. If you are not yet a customer, try our 30-day free trial to test the latest features.
You can always contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!