MENU
useActionData
useActionData() returns whatever the current route's action function returned, from its most recent call. Unlike useLoaderData(), there's nothing to show at first: useActionData() is undefined until a submission has actually been made to that route's action during this session, which makes it a natural fit for redisplaying validation errors right next to the form that produced them.
Submitting a <Form method="post"> (imported from "react-router", not a plain HTML form) posts its data to the current route's action instead of causing a full page reload. The action reads the submission with request.formData(), and can either return data directly – picked up by useActionData() in the component that rendered the form – or return redirect() to send the user somewhere else, in which case there's no form left around to call useActionData() on that route.
import React from "react";
import { createBrowserRouter, Form, useActionData, redirect } from "react-router";
import { RouterProvider } from "react-router/dom";
const router = createBrowserRouter([
{
path: "/signup",
element: <SignupForm />,
action: async ({ request }) => {
let formData = await request.formData();
let username = formData.get("username") || "";
if (username.length < 3) {
return { error: "Username must be at least 3 characters." };
}
await createUser(username);
return redirect("/welcome");
},
},
{ path: "/welcome", element: <h2>Welcome aboard!</h2> },
]);
export default function UseActionDataExample() {
return <RouterProvider router={router} />;
}
function SignupForm() {
let actionData = useActionData();
return (
<Form method="post">
<p>
<label>
Username: <input name="username" />
</label>
</p>
{actionData?.error && (
<p style={{ color: "red" }}>{actionData.error}</p>
)}
<button type="submit">Sign up</button>
</Form>
);
}
function createUser(username) {
return fetch("/api/users", {
method: "POST",
body: JSON.stringify({ username }),
});
}A redirect() from an action is why useActionData() is only ever read on the same route that owns the action – once the user has been sent elsewhere, there's nothing left to redisplay. Compare with useLoaderData, which always has something to return the moment its route renders.