Mastering Form Handling in React 19: Actions and new Hooks
written by Idriss Douiri
4 min read
React 19 comes with many features especially the improvement about working with forms including actions and new hooks. Let’s take a look and see how we can use these new features
At the time of writing this article, React 19 is still in the canary version.
run this command to upgrade from React 18
npm install react@rc react-dom@rc
Form Actions
Actions are functions triggered by form submission which can run on the client or the server using the “use server” directive.
these functions can be passed to a form’s action
attribute or throw the buttons’s formAction
prop.
import { myAction } from "./actions"
async function logAction(formData) {
console.log(formData.get("content"))
}
export default function App() {
return (
<form action={myAction}>
<input type="text" name="content" />
<button type="submit">submit</button>
<button formAction={logAction}>log</button>
</form>
)
}
The benefits of using actions:
- Form submission even before the JavaScript bundle loads.
- Ease of form management with the new hooks.
useFormStatus
The useFormStatus hook provides information about the last submission of the parent’s form
const { pending, data, method, action } = useFormStatus();
For this hook to work it needs to be a child component nested inside a form element, and it returns a status object with 4 properties including:
data
: the data submitted with the form as a FormData object.pending
: a boolean value. If true, the form is still processing the action.
A great use case of this hook is by using the pending value to disable the submit button preventing the user from submitting the form multiple times.
import { useFormStatus } from "react-dom";
import { formAction } from "./actions";
function Submit() {
const { pending } = useFormStatus()
return <button disabled={pending}>{pending ? "submitting..." : 'submit'}</button>
}
export default function App() {
return (
<form action={formAction}>
<input type="text" name="name" />
<Submit />
</form>
);
}
useActionState
useActionState (useFormState previously) is another stateful hook similar to the useState() that allows you to update a State variable based on form submission or button click.
const [state, formAction] = useActionState(fn, initialState);
This hook takes 2 arguments, a function to be called when the form is submitted, along with the initial state, and returns an array with two values: the current state and an action to be used in the form.
the fn parameter receives the previous state as a formData object, and the returned value gets set as the next state.
Here is a simple counter example using useActionState():
import { useActionState } from "react";
async function increment(previousState, formData) {
return previousState + 1;
}
function StatefulForm({}) {
const [state, formAction] = useActionState(increment, 0);
return (
<form>
{state}
<button formAction={formAction}>Increment</button>
</form>
)
}
useOptimistic
useOptimistic hook allows you to show optimistic updates in the UI providing a smooth User Experience while an action is still processing.
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
It takes two arguments:
state
: The state that you want to optimistically update.updateFn
: A function that takes the current state and the optimistic value and returns the new optimistic state.
and returns an array with two values:
optimisticState
: This is the state variable that holds the optimistic value.addOptimistic
: The function you call to trigger an optimistic update. It takes an optimistic value and passes it to the updateFn to calculate the new optimistic state.
This hook is great to use in social media apps when you want to give instant feedback to the user without waiting for the server’s response (e.g.: liking a post, or sending a message…)
Here is a todo app example with useOptimistic hook:
import { useState, useOptimistic } from 'react';
import { saveTodo } from "./server";
function TodoList() {
const [todos, setTodos] = useState([]);
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(todos, newTodo) => [...todos, newTodo]
);
const addTodo = async (formData) => {
addOptimisticTodo(formData.get("todo"));
const todo = await saveTodo(formData.get("todo"))
setTodos(todos => [...todos, todo])
};
return (
<>
<form action={addTodo}>
<input type="text" name="todo">
<button>Add Todo</button>
</form>
{/* Render optimisticTodos */}
</>
);
}