The world of web development is constantly evolving, with new libraries and technologies emerging regularly. React, one of the most widely used libraries, has recently announced a significant update with the release of React 19 beta version on April 25, 2024. This new version introduces a host of exciting features, including four new hooks that promise to revolutionize the way we handle forms and asynchronous operations. In this comprehensive article, we will delve into these new hooks, exploring their functionality and practical applications through detailed examples.
I. Form Handling with New Hooks
Traditionally, form handling in React has involved managing state variables, handling submissions, and displaying pending states. However, the introduction of three new hooks in React 19 – useFormStatus
, useActionState
, and useOptimistic
– promises to streamline this process significantly.
1. useFormStatus Hook
The useFormStatus
hook provides valuable information about the form's status during submission, eliminating the need for separate state variables to track pending states. This hook, imported from react-dom
, returns an object containing two properties: pending
(a boolean indicating whether the form is in a pending state) and data
(an object of type FormData
containing the values of the form fields). [1]
Here's an example of how to use the useFormStatus
hook within a component:
import { useFormStatus } from 'react-dom';
const Form = () => {
const { pending, data } = useFormStatus();
return (
<div>
<input type="text" name="username" placeholder="Enter your name" />
<button disabled={pending} type="submit">
Submit
</button>
{pending && <p>Submitting {data?.get("username")}...</p>}
</div>
);
};
const FormStatusWithHooks = () => {
return (
<form
action={async () => {
await submitAction();
}}
>
<Form />
</form>
);
};
In this example, the useFormStatus
hook is used within the Form
component to access the form's status and data. The pending
state is used to disable the submit button during submission, and the data
object is used to display the submitted form data.
2. useActionState Hook
The useActionState
hook simplifies the process of managing form state and handling submissions. It takes two parameters: submitData
(a function called when the form is submitted) and initialState
(the initial value of the state). The hook returns an array containing the current state and a new formAction
function that can be passed to the action
prop of the form element.
Here's an example of how to use the useActionState
hook:
import { useActionState } from 'react';
import { submitActionWithCurrentState } from '../../actions';
const ActionStateComponent = () => {
const [state, formAction] = useActionState(submitActionWithCurrentState, {
users: [],
error: null,
});
return (
<form action={formAction}>
<div>
<input type="text" name="username" placeholder="Enter your name" />
<input type="number" name="age" placeholder="Enter age" />
<button type="submit">Submit</button>
</div>
<div className="error">{state?.error}</div>
{state?.users?.map((user) => (
<div key={user.username}>
Name: {user.username} Age: {user.age}
</div>
))}
</form>
);
};
In this example, the useActionState
hook is used to manage the state of a form that submits user information (name and age). The submitActionWithCurrentState
function updates the state with the new user data or displays an error if a user with the same name already exists. The formAction
function is passed to the action
prop of the form element, and the component re-renders with the updated state after form submission.
3. useOptimistic Hook
The useOptimistic
hook allows you to optimistically update the UI while an asynchronous operation is still in progress. This hook takes two parameters: actualState
(the value of the optimistic state when no action is pending) and updateFn
(an optional function that calculates the optimistic state based on the actual state and the new value). It returns an array containing the optimistic state and a function to set the optimistic state.
Here's an example of how to use the useOptimistic
hook:
import { useOptimistic, useState } from 'react';
import { submitTitle } from '../../actions';
const OptimisticComponent = () => {
const [title, setTitle] = useState('Title');
const [optimisticTitle, setOptimisticTitle] = useOptimistic(title);
const [error, setError] = useState(null);
const pending = title !== optimisticTitle;
const handleSubmit = async (formData) => {
setError(null);
setOptimisticTitle(formData.get('title'));
try {
const updatedTitle = await submitTitle(formData);
setTitle(updatedTitle);
} catch (e) {
setError(e);
}
};
return (
<div>
<h2>{optimisticTitle}</h2>
<p>{pending && 'Updating...'}</p>
<form action={handleSubmit}>
<input type="text" name="title" placeholder="Change Title" />
<button type="submit" disabled={pending}>
Submit
</button>
</form>
<div className="error">{error && error}</div>
</div>
);
};
In this example, the useOptimistic
hook is used to optimistically update the page title while an asynchronous operation is in progress. The optimistic title is displayed immediately after form submission, and the actual title is updated once the asynchronous operation is complete. If the operation fails, the optimistic state reverts to the previous state, and an error message is displayed.
II. useExisting Hook
While not a new hook, the use
method introduced in React 19 provides a convenient way to read the value of a Promise or a Context inside a component. This method can be called inside components, hooks, and even conditional statements like if
and for
loops.
1. Reading Contexts with use
When working with contexts, the use
method behaves similarly to the useContext
hook, returning the value provided by the context for use within the component.
import { createContext, use } from 'react';
import '../../styles.css';
const ThemeContext = createContext(null);
const UseHookWithContext = () => {
return (
<ThemeContext.Provider value="dark">
<MyComponent />
</ThemeContext.Provider>
);
};
const MyComponent = () => {
const theme = use(ThemeContext);
return (
<div className={`myContainer theme-${theme}`}>
<h2>Hi There!</h2>
</div>
);
};
In this example, the use
method is used to read the value of the ThemeContext
within the MyComponent
component. The retrieved value is then used to apply a CSS class based on the theme.
2. Reading Resolved Values of Promises with use
The use
method can also be used to read the resolved value of a Promise within a component. It integrates with the Suspense API to display a temporary message while the Promise is pending.
"use client";
import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";
const DataContainer = ({ dataPromise }) => {
return (
<Suspense fallback={<p>Fetching Data...</p>}>
<DataComponent dataPromise={dataPromise} />
</Suspense>
);
};
const DataComponent = ({ dataPromise }) => {
const data = use(dataPromise);
return <div>{data && data}</div>;
};
In this example, a Promise created in a server component is passed to the DataContainer
component as dataPromise
. The DataContainer
component renders the DataComponent
within a Suspense
boundary, which displays a fallback message ("Fetching Data...") while the Promise is pending.
Inside the DataComponent
, the use
method is called with the dataPromise
, which returns the resolved value of the Promise. This value is then rendered within a div
element. If the Promise rejects, the ErrorBoundary
component (not shown) will catch the error and handle it appropriately.
The use
method simplifies the process of reading Promise values within components, eliminating the need for complex state management and conditional rendering based on the Promise's state. It provides a more declarative and straightforward approach to working with asynchronous data in React applications.