Jacob Paris
← Back to all content

Multiple forms and actions on a page with Remix

Remix uses a file-based approach to routing, so each file in your /routes folder corresponds to a particular URL.

A page's loader function can be treated like a handler for all GET and HEAD requests to that endpoint, while a page's action function can be treated like a handler for all other requests (POST, PUT, PATCH, DELETE, etc…)

Since all forms on a page will submit to the same action, there's no built-in way of determining which form was submitted.

Multiple forms on a page

To work around this, you can add a hidden input with a name and value to your submit button. The name will be the same for all forms on the page, and the value will be unique to that form.

html
<input type="hidden" name="intent" value="LIKE" />

Multiple actions from a single form

When you submit a form, the browser will include all of its form elements, including hidden inputs, in the request body.

One exception to this rule is the submit buttons themselves. The only submit button that will be included in the request body is the one that was clicked.

So if you want to submit multiple actions from a single form, you can add name="intent" to the submit button

tsx
<form method="post">
<input
type="hidden"
name="tweetId"
value="asdfasdfasdfasdf"
/>
<button type="submit" name="intent" value="LIKE">
Like
</button>
<button type="submit" name="intent" value="RETWEET">
Retweet
</button>
<button type="submit" name="intent" value="REPLY">
Reply
</button>
<button type="submit" name="intent" value="SHARE">
Share
</button>
</form>

Determining which action to take

In your action, you can use the value of the intent input to determine which action to take.

ts
export async function action({
request,
}: ActionFunctionArgs) {
const formData = await request.formData()
const intent = formData.get("intent")
if (intent === "LIKE") {
return likeTweetAction(args)
}
if (intent === "RETWEET") {
return retweetAction(args)
}
if (intent === "REPLY") {
return replyAction(args)
}
if (intent === "SHARE") {
return shareAction(args)
}
throw new Error("Unknown action")
}

For type safety, you can use zod to validate the inputs for each action

Professional headshot
Moulton
Moulton

Hey there! I'm a developer, designer, and digital nomad building cool things with Remix, and I'm also writing Moulton, the Remix Community Newsletter

About once per month, I send an email with:

  • New guides and tutorials
  • Upcoming talks, meetups, and events
  • Cool new libraries and packages
  • What's new in the latest versions of Remix

Stay up to date with everything in the Remix community by entering your email below.

Unsubscribe at any time.