Group and parse fields in Remix / React Router v6
React Router v6 introduced the ability to have route loaders and actions, changing the paradigm on how you perform data mutations having a simple mental model where HTML + HTTP requests are the main characters.
You can leave aside having tons of useState and callbacks to manage the state. If you have been in the development world long enough, this may sounds familiar from the good old times.
Let's dive in and imagine you have the following scenario: You have a group of shopping cart items you want to edit, so any cart can have a N amount of items. It will look something like this
const items = [
{ id: 1, name: "Padel racket", quantity: 1, price: 13000 },
{ id: 2, name: "Black shorts", quantity: 1, price: 4499 },
{ id: 3, name: "Wilson Rush Pro", quantity: 1, price: 9999 },
...
];
In order to work with router actions we only need to set up a name for the input, wrap it in a <Form>
tag from react-router and the value will be sent to the action, no state is needed! Pretty awesome.
Form structure
To maintain that structure in the route action you need to set your fields to be grouped by "children" of a parent field, and give each "child" a numeric index. Yes, like an array of objects but with the HTML naming. Something like this:
import { Form } from "react-router-dom";
// The action url should be the one in which you have the route action
<Form action="/order" method="put">
{items.map((item, index) => (
<input type="hidden" name={`cart[${index}][id]`} value={item.name} />
<input type="text" name={`cart[${index}][name]`} value={item.name} />
<input type="text" name={`cart[${index}][quantity]`} value={quantity} />
<input type="text" name={`cart[${index}][price]`} value={item.price} />
)}
</Form>
If you need some fields in the route action that are not editable, don't forget about the <input type="hidden" />
Route action
Now in the route action, we should handle that input. First of all you need to install the qs package:
npm install qs
That's because the request.formData()
is not powerful enough to format it in a proper way so you will end up doing some kind of black magic to parse it, but qs
takes care of all of that.
Now in the route action:
export async function orderEditAction({ request, params }) {
// Get the request as a text
const data = await request.text();
const { cart } = parse(data);
console.log("OrderEditAction", cart);
}
That's all! Just make sure you have the structure you want represented in the HTML and use qs
to parse it.