In February this year, React announced the release of version 19, finally updating a version number that had been static for two years (React 19 is Coming, What's New?). Andrew Clark, a member of the React team, confirmed that the new version would be released in March or April.
As they say, "deadlines are the first productivity driver." Here we are at the end of April, and the new version, 19.0.0-Beta, has just been released right on schedule.
Although it's only a Beta version, it has already generated considerable excitement within the community:
- Dan commented: "they did what."
- Andrew Clark commented: "React 19: Never forwardRef again."
- Josh W. Comeau commented: "Lots of nice quality-of-life improvements here!"
The only disappointment is, as confirmed by React member Lauren, that the React Compiler has been delayed again. It was originally named "React Forget," a fitting title given its delays.
The features of the 19.0.0-Beta release include:
- An Actions feature
- Three new hooks
- A new API
- Simplified usage of ref and context
- Various supportive updates and enhanced server-side capabilities
Let's delve into each of these.
Follow me:
- Github: https://github.com/afzalimdad9
- Medium: https://medium.com/@afzalimdad9
Actions
Actions are not a specific API but a general term for methods that simplify data handling in requests. A valid Actions setup should simplify asynchronous operations, allowing developers to focus more on business logic rather than state management.
Here's an example to illustrate the role of Actions. Suppose we have a form where users can update their name. Previously, we might use useState
to manually manage form state, error state, and submission status. The code might look something like this:
function UpdateName() {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, setIsPending] = useState(false);
const handleSubmit = async () => {
setIsPending(true);
const error = await updateName(name);
setIsPending(false);
if (error) {
setError(error);
return;
}
redirect("/path");
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
With React 19's Actions, this situation is optimized. We can now use the useTransition
hook to handle form submissions, which automatically manages the pending state, making our code much cleaner:
function UpdateName() {
const [name, setName] = useState("");
const [error, setError] = useState(null);
const [isPending, startTransition] = useTransition();
const handleSubmit = async () => {
startTransition(async () => {
const error = await updateName(name);
if (error) {
setError(error);
return;
}
redirect("/path");
});
};
return (
<div>
<input value={name} onChange={(event) => setName(event.target.value)} />
<button onClick={handleSubmit} disabled={isPending}>
Update
</button>
{error && <p>{error}</p>}
</div>
);
}
In the handleSubmit
function, the logic for the asynchronous request is encapsulated within the callback passed to startTransition
. When startTransition
is invoked, React immediately sets isPending
to true, indicating that the transition (request) is underway. React then performs the startTransition
callback asynchronously in the background. Once the request is complete, React automatically switches isPending
back to false.
This functionality binds isPending
to the disabled
attribute of the submit button, so the button automatically becomes disabled during the request, preventing double submissions.
Summarizing React's conventions for Actions:
- Naming Convention: Functions using asynchronous transitions can be referred to as "Actions."
- Pending State: Actions automatically manage the pending state of data submissions. When a request is initiated, the pending state is automatically activated, and it resets once the final state updates. This ensures users receive feedback while data is submitted and the pending state is cleared after the request completes.
- Optimistic Updates: Actions support optimistic updates, displaying the correct submission result to users while the request is pending. If the request ultimately fails, the optimistic update automatically reverts to its original state.
- Error Handling: Actions include built-in error handling. When a request fails, you can use error boundaries to display error messages.
-
Form Support: elements now support passing functions to
action
andformAction
attributes. By passing a function to theaction
attribute, you can handle form submissions with Actions, automatically resetting the form after submission. This simplifies the process of handling forms, making it more intuitive and efficient.
Three New Hooks
Why discuss Actions first? Because based on Actions, React 19 introduces three new hooks, each designed to simplify the complex handling of state by developers.
useOptimistic
The main purpose of useOptimistic
is to allow us to assume success in asynchronous operations and update the state accordingly while waiting for the actual results.
Here's a basic usage example:
import { useOptimistic } from 'react';
function AppContainer() {
const [optimisticState, addOptimistic] = useOptimistic(
state,
// updateFn
(currentState, optimisticValue) => {
// merge and return new state
// with optimistic value
}
);
}
In this setup:
- state: Initial state and the state returned when no operations are in progress.
-
updateFn(currentState, optimisticValue): A pure function that takes the current state and the optimistic value passed by
addOptimistic
, returning the merged optimistic state. -
optimisticState: The optimistic state. If no operations are in progress, it equals state; otherwise, it equals the result of
updateFn
. -
addOptimistic: A function for triggering optimistic updates, accepting an
optimisticValue
of any type and passing it toupdateFn
.
useOptimistic
has a wide range of applications, such as form submissions, liking, bookmarking, deleting, and other scenarios that require immediate feedback.
Here's an example of optimistic updating for data deletion:
import React, { useState } from 'react';
import { useOptimistic } from 'react';
function AppContainer() {
// Default data
const [state, setState] = useState([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
]);
// Define the update function, which updates the state based on the current state and the optimistic value (the ID of the item to be deleted)
const updateFn = (currentState, optimisticId) => {
return currentState.filter(item => item.id !== optimisticId);
};
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
// Delete item
const deleteItem = async (itemId) => {
// Optimistically update the UI first
addOptimistic(itemId);
// Simulate API request delay
setTimeout(() => {
// Assume this is the API delete call, update the actual state after completion
setItems(currentItems => currentItems.filter(item => item.id !== itemId));
}, 2000);
};
return (
<div>
<h1>Optimistically Deleting Items</h1>
<ul>
{optimisticState.map(item => (
<li key={item.id}>
{item.name} <button onClick={() => deleteItem(item.id)}>Delete</button>
</li>
))}
</ul>
</div>
);
}
export default AppContainer;
useActionState
useActionState
, formerly known as useFormState
, has a new name in version 19, and its return parameters have changed (oddly, React has not yet updated the useActionState
documentation, do they think developers don't read documentation? 🐶).
Here is the latest basic usage of useActionState
:
const [state, action, pending] = useActionState(fn, initialState, permalink?);
The return parameters include:
-
state: Represents the current state. At the first render, it equals the initial state
initialState
. After an operation, it will be the most recent result. -
action: This is a function used to perform operations. When this function is called, it will trigger the execution of
fn
and update the state. -
pending: This is a new parameter, a
boolean
value indicating whether an operation is currently being performed. If an operation is in progress, it istrue
; otherwise, it isfalse
.
The parameters are:
- fn: This is a function that is triggered when the action is called, subsequently returning a new value.
- initialState: This is the initial value, set to null if there is no initial value.
- permalink: This is an optional string parameter, typically used in conjunction with server actions.
Here's an example of using useActionState
with form action to implement name update functionality. If the update fails, an error
is displayed on the page; if successful, the page redirects to the updated page:
import { useActionState } from 'react';
function ChangeName({ name, setName }) {
// Using useActionState to create a state associated with form operations
const [error, submitAction, isPending] = useActionState(
// First parameter: Form operation function
async (previousState, formData) => {
// Define the logic for form operations here
// This function will be called upon form submission
// It receives two parameters:
// - previousState: The previous state, initially null, then the return value of the last operation
// - formData: A form data object, can retrieve form fields using formData.get("name")
const error = await updateName(formData.get("name"));
// If there is an error during the operation, return the error message
if (error) {
return error;
}
// If the operation is successful, perform a redirection
redirect("/path");
},
// Second parameter: Initial state, set here as null since the initial state is not crucial
null
}
// Return the form along with related state and actions
return (
<form action={submitAction}>
<input type="text" name="name" defaultValue={name} />
<button type="submit" disabled={isPending}>
Submit
</button>
{/* Display error messages if any */}
{error && <p>{error}</p>}
</form>
);
}
useFormStatus
useFormStatus
is used to retrieve status information about form submissions. Its basic usage is as follows:
const { pending, data, method, action } = useFormStatus();
Where:
-
pending: A boolean value indicating whether the parent
<form>
is currently submitting. If true, the form is in the process of being submitted; otherwise, it is false. -
data: An object implementing the FormData interface, containing data from the parent
<form>
that is currently being submitted. If there is no ongoing submission or no parent<form>
, it isnull
. -
method: A string value indicating the HTTP method used by the parent
<form>
, which can be either get or post. -
action: A reference to the function passed to the parent
<form>
's action attribute. If there is no parent<form>
, this property isnull
.
For example, developers can retrieve form status using useFormStatus
in a form action:
import { useFormStatus } from "react-dom";
import action from './actions';
function Submit() {
const status = useFormStatus();
return <button disabled={status.pending}>Submit</button>
}
export default function App() {
return (
<form action={action}>
<Submit />
</form>
);
}
This approach may feel both familiar and new. If it reminds you of context, you're on the right track. You can consider useFormStatus
as a replacement for some of the capabilities of the context provider, but with a more streamlined syntax.
It's important to note two things about using useFormStatus
:
- The
useFormStatus
Hook must be called within components that are rendered inside a<form>
. -
useFormStatus
only returns the status information of the parent<form>
, not any other in the same component or its child components.
A New API - use
Previously categorized among hooks, the use has now been documented under APIs in React 19, thus becoming a new API.
use
is used to read values from resources within a component, where the resource could be a Promise or a context.
Here's how it typically works:
import { use } from 'react';
function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
const theme = use(ThemeContext);
// …
}
use
is mainly intended for higher-level frameworks like Next.js. For instance, it's recommended to use async
…await
instead of use if data fetching occurs in server components in Next.js. If fetching happens in client components, it's suggested to create a Promise in server components and pass it to client components as props.
use
can also be used alongside Suspense boundaries. If a component calling use
is wrapped in a Suspense boundary, the specified fallback will be displayed. Once the Promise resolves, the fallback is replaced by the returned data. If the Promise
is rejected, the nearest error boundary's fallback will be displayed.
Simplified ref and context Usage
If you primarily utilize React's client-side capabilities, the changes discussed in this section will likely be of most interest to you.
Ref Discards forwardRef
Remember the dread of being dominated by forwardRef
? With React 19, we can finally discard forwardRef
. From now on, refs can be passed as props.
For example, suppose we have a functional component TextInput
, a simple input box that accepts a placeholder
attribute for placeholder text. Now, we want to obtain a reference to the input box in the parent component to focus on it when necessary. Here's how you could write it:
import React, { useRef } from 'react';
// Define a function component TextInput
function TextInput({ placeholder, ref }) {
return <input placeholder={placeholder} ref={ref} />;
}
// Parent component
function ParentComponent() {
// Create a ref to store the input box's reference
const inputRef = useRef(null);
// In some event handler, obtain the input box's reference and focus on it
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
{/*
Pass the inputRef to the TextInput component,
allowing the ref to be used internally within the TextInput
*/}
<TextInput placeholder="Enter your name" ref={inputRef} />
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
export default ParentComponent;
Isn't this mentally less burdensome than using forwardRef?
Context as Provider
Starting in React 19, developers can directly use <Context>
as a provider, rather than using <Context.Provider>
.
Suppose we have a ThemeContext
used to manage theme information. In React 19, we can use <ThemeContext>
as a provider like this:
import React, { createContext } from 'react';
// Create a theme context
const ThemeContext = createContext('');
// App component as the theme provider
function App({ children }) {
return (
<ThemeContext value="dark">
{children}
</ThemeContext>
);
}
In the future, Context.Provider
will be deprecated and removed.
Other Updates
This release also includes various supportive features and enhancements to server-side capabilities that are less commonly used in pure client-side React development, hence they are summarized briefly:
- Server components and server
actions
are becoming stable features. These concepts are well-known to those familiar with Next.js/Remix but are not typically used by those who do not use these frameworks. -
useDeferredValue
has been updated to include a second parameter, optionally used to specify an initial value. The new usage ofuseDeferredValue
is now:const value = useDeferredValue(deferredValue, initialValue?);
- Support for writing document metadata directly in React code, i.e., writing
<title>
,<link>
, and<meta>
tags in page components will automatically add them to the application's<head>
:
function BlogPost({post}) {
return (
<article>
<h1>{post.title}</h1>
<title>{post.title}</title>
<meta name="author" content="Josh" />
<link rel="author" href="<https://twitter.com/joshcstory/>" />
<meta name="keywords" content={post.keywords} />
<p>
Eee equals em-see-squared...
</p>
</article>
);
}
- Support for writing stylesheets directly in React code, i.e.,
<link rel="stylesheet" href="…">
tags written in page components will automatically be added to the<head>
. - Support for writing
<script async="" src="…">
tags, which will also automatically be added to the<head>
. - Support for preloading resources, which will also automatically be added to the
<head>
:
import { prefetchDNS, preconnect, preload, preinit } from 'react-dom'
function MyComponent() {
preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly
preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font
preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet
prefetchDNS('https://...') // when you may not actually request anything from this host
preconnect('https://...') // when you will request something but aren't sure what
}
Conclusion
To conclude, let me quote Huang Xuan: "Probably the single most critical principle I've learned from React is to be fearless in defining new conceptual abstractions and never compromise on the accuracy and composability of these definitions."
About Me
Full-stack engineer, Next.js open-source craftsman, AI advent proponent.
This year, I am committed to the development of open-source projects in the Next.js and Node.js domains and to sharing knowledge in these fields.
Follow me:
- Github: https://github.com/afzalimdad9
- Medium: https://medium.com/@afzalimdad9