Form actions in Remix are handled by the action
function that runs on your server. They work very much like loaders, except they're called when you make a DELETE
, PATCH
, POST
, or PUT
to your route.
Remix's action/loader structure is incredibly straightforward and easy to grasp! If you've got some experience with data flow in client-server applications, you'll find this a piece of cake. But before we answer how we can handle multiple form actions in Remix, let's understand how the data flow works.
The Remix Fullstack Data Flow
In the Remix Fullstack Data Flow, the loader
fetches data from the backend and passes that data to your component. The action
passes your component data to the backend. After the action
finishes, loaders are revalidated and return the current state of the backend to the component.
This is how Remix keeps your backend and frontend data in sync without any additional code to do refetches.
Also, in Remix each route has one loader
and one action
.
The issue arises when, on the client, you want to handle different functionalities, and each functionality would require a different action on the backend.
Let's take a look at this simple Todo app:
export async function loader() {
return json({ todos: getTodos() });
}
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const { todo } = Object.fromEntries(formData.entries());
await addTodo(todo);
return json({ ok: true });
}
export default function Index() {
const { todos } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
return (
<div className="max-w-md">
<h1 className="text-2xl pb-10">Todos</h1>
<Form method="post" className="flex flex-col gap-4">
<label htmlFor="todo">Title</label>
<input type="text" name="todo" id="todo" autoComplete="off" />
<button>Add</button>
</Form>
<ul>
{todos.map((todo) => (
<li key={todo.id} className="text-lg pt-5">
{todo.text}
</li>
))}
</ul>
</div>
);
}
There is nothing fancy here, just an input and a button to submit a todo item to your list.
The problem with multiple actions
Let's say we want to extend the functionality with a button to remove all items with a single click:
<Form method="post" className="flex flex-col gap-4">
<label htmlFor="todo">Title</label>
<input type="text" name="todo" id="todo" autoComplete="off" />
<button>Add</button>
<button>Clear</button>
</Form>
But because we have only one action
handler, it doesn't matter which of these buttons we click, the action result is the same:
The solution
To work around this, we'll add two new attributes to each of these buttons: name
and value
.
I like to use name="intent"
(reminds me of Android's Intent) and a custom value
describing the action we want to run:
<Form method="post" className="flex flex-col gap-4">
<label htmlFor="todo">Title</label>
<input type="text" name="todo" id="todo" autoComplete="off" />
<button name="intent" value="add">Add</button>
<button name="intent" value="clear">Clear</button>
</Form>
Now, whenever we submit our form data, the request's request.formData
will contain an additional field formData.get("intent")
.
We can use this value to decide what to do next:
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const { todo } = Object.fromEntries(formData.entries());
const intent = formData.get("intent");
switch (intent) {
case "add": await addTodo(todo); break;
case "clear": clearTodos(); break;
default: throw new Error("Unexpected action");
}
return json({ ok: true });
}
And finally, here's how the UI works after taking into consideration the value of intent
:
You can experiment with the above pattern in the Codesandbox I created.
Further reading
Remix Tutorial - I highly recommend you do the ~30-minute long, comprehensive, official tutorial explaining basic Remix concepts such as data mutations, data loading, routing, styling helpers, etc.
Community - check out the Remix community links, including their Discord server and a vast collection of Remix-related links.
The Ultimate Full Stack Framework for 2024: Remix - Remix blew my mind with its simplicity and philosophy. After using it only for a few days, I wrote about why it is my big bet for 2024.
If you enjoyed this article, please like it and share it with developers who would find it useful.
Thanks,
Akos
Top comments (0)