This content originally appeared on Telerik Blogs and was authored by Hassan Djirdeh
Explore how the React Actions suite of features and hooks in React 19 can simplify state management and improve the user experience of your applications.
When developing web applications with React, state management and form handling is a critical skill set. As user interactions often involve forms—from logging in to submitting data—efficient and clear management of form state is essential for responsive and interactive web applications.
React 19 introduces React Actions, a suite of features and hooks designed to streamline complex state transitions and interactions within React applications. This article explores how these new tools can enhance your development workflow and improve the user experience of your applications.
React Actions
Before React 19 and the concept of React Actions, handling form submissions typically involved using useState or useReducer to track form state, and useEffect for handling side effects like data fetching. This could lead to complex and hard-to-maintain code, especially in large applications with multiple forms interacting with various backends.
A simple example of a form component handling such async updates can look something like the following:
import React, { useState, useCallback } from 'react';
const submitForm = async () => {/* form submit */};
export function Component() {
// create form state
const [formState, setFormState] = useState(null);
const [isPending, setIsPending] = useState(false);
// handle form submission
const formAction = useCallback(async (event) => {
event.preventDefault()
setIsPending(true)
try {
const result = await submitForm()
setFormState(result)
} catch (error) {
setFormState({ message: 'Failed to complete action' })
}
setIsPending(false)
}, []);
// display form template
return (
<form onSubmit={formAction}>
{/* Form Template */}
</form>
);
}
In the example above, the component handles the form’s state and processes form submissions asynchronously. When the form is submitted, the submitForm()
function sends the form data to a server and receives a response. The component then updates its state to provide feedback to the user about the submission process.
In React 18, the concept of transitioning the UI from one view to another in a non-urgent manner was referred to as transitions. React 19 extends this functionality by supporting the use of async functions in transitions. The useTransition hook can now be utilized to manage the display of loading indicators or placeholders during asynchronous data fetching.
In React 19, the concept of transitions is taken a step further as functions that use async transitions are now referred to as Actions. Actions simplify the management of form-related state changes, especially in scenarios involving asynchronous operations such as data fetching or submission. These Actions encapsulate the patterns needed to handle both the state and side effects of UI components, which were previously managed by combining several state hooks and effect hooks.
There now exist a few specialized hooks to manage Actions like what we’ve seen above and the first we’ll take a look at is the useActionState
hook.
useActionState
The useActionState hook is a core part of React Actions, designed to simplify the management of form state and asynchronous operations. It encapsulates the logic for handling form submissions, updating state, and managing loading indicators.
The useActionState()
hook takes three parameters:
- An “action” function, which is called when the form action is triggered
- An initial state object, setting the starting state of the form before any user interaction
- [Optional] A permalink that points to the unique page URL modified by this form
It returns three values in a tuple:
- The current state of the form
- A function to trigger the form action
- A boolean indicating if the action is pending
import { useActionState } from "react";
export function Component() {
const [state, dispatch, isPending] = useActionState(action, initialState, permalink)
// ...
}
The action function, which is the first argument passed to the useActionState
hook, is invoked when the form is submitted. It returns the form state we expect to transition to, whether the submission is successful or encounters errors. This function receives two parameters: the current state of the form and the form data at the moment the action was triggered.
Here’s an example of creating an action()
function that calls a hypothetical submitForm()
function, which then triggers an API call to send the form data to a server. If the action is successful, it returns a form state object representing the updated state of the form. If the action fails, it returns a form state object reflecting the error state, potentially including error messages or indicators to help the user correct the issue.
import { useActionState } from "react";
const submitForm = async (formData) => {
/* form submit fn that calls API */
};
const action = async (currentState, formData) => {
try {
const result = await submitForm(formData);
return { message: result };
} catch {
return { message: "Failed to complete action" };
}
};
export function Component() {
const [state, dispatch, isPending] = useActionState(
action,
null,
);
// ...
}
With our useActionState()
hook set up, we can now utilize the form state, dispatch()
function and isPending
values in our form template.
With React 19, <form>
elements now have an action
prop that can receive an action function that will be triggered when a form is submitted. Here is where we’ll pass down the dispatch
function from our useActionState()
hook.
import { useActionState } from "react";
const submitForm = async (formData) => {
/* form submit fn that calls API */
};
const action = async (currentState, formData) => {
try {
const result = await submitForm(formData);
return { message: result };
} catch {
return { message: "Failed to complete action" };
}
};
export function Component() {
const [state, dispatch, isPending] = useActionState(
action,
null,
);
return (
<form action={dispatch}>
{/* ... */}
</form>
);
}
We can show the form state
within our template and use the isPending
value to indicate to the user when the asynchronous action is in progress.
import { useActionState } from "react";
const submitForm = async (formData) => {
/* form submit fn that calls API */
};
const action = async (currentState, formData) => {
try {
const result = await submitForm(formData);
return { message: result };
} catch {
return { message: "Failed to complete action" };
}
};
export function Component() {
const [state, dispatch, isPending] = useActionState(
action,
null,
);
return (
<form action={dispatch}>
<input type="text" name="text" disabled={isPending} />
<button type="submit" disabled={isPending}>
Add Todo
</button>
{/*
display form state message to convey when
form submit is successful or fails.
*/}
{state.message && <h4>{state.message}</h4>}
</form>
);
}
Thanks to these new changes in React, there’s no longer a need to manually handle pending states, errors and sequential requests when working with async transitions in forms. Instead, we can access these values directly through the useActionState()
hook!
For a more detailed example that interacts with the publicly available dummyjson API, check out this GitHub Gist.
useOptimistic
As part of the changes designed to enhance form handling and component interaction within forms, React 19 introduces a new hook called useOptimistic, which is designed to improve the user experience by allowing developers to update the UI optimistically when an async action is underway. This means that the UI behaves as if the desired changes have occurred without waiting for server confirmation, thereby making the app feel faster and more responsive.
Optimistic updates are particularly useful in situations where the user interacts frequently with the UI, such as liking a post, updating a status, or adding items to a list. The useOptimistic
hook helps manage the UI state by assuming successful completion of an operation while still handling the actual server response to revert changes if necessary.
import { useOptimistic } from "react";
export function Component({ message, updateMessage }) {
// create "optimistic" state property
const [optimisticMessage, setOptimisticMessage] =
useOptimistic(message);
const submitForm = async (formData) => {
const newMessage = formData.get("text");
// before triggering API change, set new value optimistically
setOptimisticMessage(newMessage);
// update actual state when API submission resolves
const updatedName = await submitToAPI(newMessage);
updateMessage(updatedName);
};
return (
<form action={submitForm}>
{/* show optimistic value */}
<p>{optimisticMessage}</p>
<input type="text" name="text" />
<button type="submit">Add Message</button>
</form>
);
}
In the above component example, the useOptimistic
hook manages optimistic updates for the message
state passed down as a prop.
When the user submits the form by clicking the “Add Message” button, the submitForm()
function is triggered. Before making the API request to update the message, the setOptimisticMessage()
function is called with the new message value from the form data. This instantly updates the UI to reflect the optimistic change, providing immediate feedback to the user.
Once the update completes or fails, React will automatically revert to the message
prop value.
useFormStatus
useFormStatus is another new addition to React Actions, designed to allow nested child components to access form-related information from their parent forms, functioning much like a context provider.
useFormStatus
facilitates better state sharing across form components, especially in complex forms where multiple nested components might depend on the overall form state or need to react to form submission events.
import { useFormStatus } from "react";
import action from './actions'
export function NestedComponent() {
/* access form information */
const { pending, data, method, action } = useFormStatus();
return (
/* template */
);
}
export default function App() {
return (
<form action={action}>
<NestedComponent />
</form>
);
}
In the above code example, NestedComponent
utilizes the useFormStatus
hook to access and display the form’s status and metadata, such as whether a submission is pending (pending
), the form’s method (method
) and the action URL (action
). This approach decouples the nested component from its parent form while still allowing it to access relevant form data and state, enhancing modularity and reusability of components within the form.
Though accessing parent form information can be achieved using Context, React 19 introduces the useFormStatus
hook to simplify handling form data within nested components.
Wrap-up
React Actions, introduced in React 19, represent a significant advancement in state management and form handling within React applications. The new suite of hooks—useActionState
, useOptimistic
and useFormStatus
—provides developers with useful tools to create more responsive and user-friendly interfaces. By simplifying common patterns such as managing asynchronous operations, implementing optimistic UI updates, and handling form submission states, these hooks reduce boilerplate code and improve overall maintainability.
For more information on React Actions, refer to the official React documentation on React 19 and its latest features.
This content originally appeared on Telerik Blogs and was authored by Hassan Djirdeh
Hassan Djirdeh | Sciencx (2024-08-06T08:42:20+00:00) React Basics: How to Handle State Management with React Actions. Retrieved from https://www.scien.cx/2024/08/06/react-basics-how-to-handle-state-management-with-react-actions/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.