useFormAction
Resolves the URL to the closest route in the component hierarchy instead of the current URL of the app. This is used internally by <Form> to resolve the action to the closest route.
import { useFormAction } from "react-router";
function SomeComponent() {
// Resolves to closest route URL
let action = useFormAction();
// "/posts" if current route is /posts
// Closest route URL + "destroy"
let destroyAction = useFormAction("destroy");
// "/posts/destroy"
}
Parameters
The action to append to the closest route URL. Defaults to the closest route URL if not provided.
relative
"route" | "path"
default:"\"route\""
The relative routing type to use when resolving the action:
"route" (default) - Relative to the route hierarchy
"path" - Relative to the URL path segments
Return Value
The resolved action URL string.
Type Declaration
declare function useFormAction(
action?: string,
options?: { relative?: "route" | "path" }
): string;
Usage Examples
import { useFormAction } from "react-router";
function MyForm() {
const action = useFormAction();
console.log(action); // Current route URL, e.g., "/posts/123"
return (
<form action={action} method="post">
<input type="text" name="title" />
<button type="submit">Submit</button>
</form>
);
}
Custom Action Path
import { useFormAction } from "react-router";
function DeleteButton({ postId }: { postId: string }) {
const deleteAction = useFormAction("delete");
// If current route is "/posts/123", this resolves to "/posts/123/delete"
return (
<form action={deleteAction} method="post">
<input type="hidden" name="id" value={postId} />
<button type="submit">Delete</button>
</form>
);
}
Multiple Actions on Same Route
import { useFormAction } from "react-router";
function PostEditor() {
const saveAction = useFormAction("save");
const publishAction = useFormAction("publish");
const deleteAction = useFormAction("delete");
return (
<div>
<form action={saveAction} method="post">
<input name="title" />
<button type="submit">Save Draft</button>
</form>
<form action={publishAction} method="post">
<button type="submit">Publish</button>
</form>
<form action={deleteAction} method="post">
<button type="submit">Delete</button>
</form>
</div>
);
}
Relative Routing
import { useFormAction } from "react-router";
// Route hierarchy: /blog/:postId/edit
function EditForm() {
// Route-relative (default) - resolves relative to route pattern
const routeAction = useFormAction("..", { relative: "route" });
// Resolves to "/blog/:postId"
// Path-relative - resolves relative to URL path
const pathAction = useFormAction("..", { relative: "path" });
// Resolves to "/blog"
return (
<div>
<form action={routeAction} method="post">
<button>Submit (route relative)</button>
</form>
<form action={pathAction} method="post">
<button>Submit (path relative)</button>
</form>
</div>
);
}
Common Patterns
import { useFormAction } from "react-router";
import { useState } from "react";
type ActionType = "create" | "update" | "delete";
function DynamicForm() {
const [actionType, setActionType] = useState<ActionType>("create");
const action = useFormAction(actionType);
return (
<div>
<select value={actionType} onChange={(e) => setActionType(e.target.value as ActionType)}>
<option value="create">Create</option>
<option value="update">Update</option>
<option value="delete">Delete</option>
</select>
<form action={action} method="post">
<input name="data" />
<button type="submit">Submit {actionType}</button>
</form>
</div>
);
}
import { useFormAction } from "react-router";
// On an index route, the action resolves with ?index parameter
function IndexForm() {
const action = useFormAction();
// On index route at "/posts", resolves to "/posts?index"
return (
<form action={action} method="post">
<button type="submit">Create Post</button>
</form>
);
}
Preserving Search Params
import { useFormAction, useLocation } from "react-router";
function FormWithSearchParams() {
const location = useLocation();
const baseAction = useFormAction();
// Append current search params to action
const action = `${baseAction}${location.search}`;
return (
<form action={action} method="post">
<input name="title" />
<button type="submit">Submit</button>
</form>
);
}
Nested Routes
import { useFormAction } from "react-router";
// Route structure:
// /dashboard
// /settings
// /profile
function ProfileForm() {
// Current route: /dashboard/settings/profile
const currentAction = useFormAction();
// "/dashboard/settings/profile"
const parentAction = useFormAction("..");
// "/dashboard/settings"
const rootAction = useFormAction("/");
// "/"
return (
<form action={currentAction} method="post">
<button type="submit">Save Profile</button>
</form>
);
}
Notes
- Available in Framework and Data modes only
- When
action is not provided, it defaults to the current route URL
- Automatically handles basename if configured in your router
- Search params from the current location are preserved when
action is null or .
- Index routes automatically append
?index to the action URL
- The
relative option affects how .. paths are resolved
How It Works
The hook resolves the action URL based on:
- The route hierarchy (when
relative: "route")
- The URL path segments (when
relative: "path")
- The current basename configuration
- Whether the route is an index route
// Example route hierarchy:
// <Route path="/blog" element={<Blog />}>
// <Route path=":postId" element={<Post />}>
// <Route path="edit" element={<Edit />} />
// </Route>
// </Route>
// On route /blog/123/edit:
useFormAction(); // "/blog/123/edit"
useFormAction("save"); // "/blog/123/edit/save"
useFormAction(".."); // "/blog/123" (relative to route)
useFormAction("..", { relative: "path" }); // "/blog/123" (relative to path)