Welcome to the EpicWeb.dev Workshop app!

This is the deployed version. Run locally for full experience.

Data Mutations

Loading "Intro to Mutations"

The web

Ever since the invention of HTML, we've had the ability to create interactive web applications thanks to the <form> element:
<form>
	<label>
		Type:
		<input name="sandwichType" />
	</label>
	<button type="submit">Create Sandwich</button>
</form>
When a form is submitted on the web (like if we were to type "ham" in the sandwichType input and click "Create Sandwich"), the browser will take all associated form elements (the sandwichType input in our example above) and "serialize" them into what's called a "query string" (e.g. sandwichType=ham) and make a request to the server at the current URL. So, if this form appears on the route /sandwiches, submitting the form will trigger a full-page refresh to the route /sandwiches?sandwichType=ham.
This is great for search pages for example, but it would be really bad for a login form (you wouldn't want someone looking over your shoulder to see your password in the URL would you?), which is why the <form> element also allows you to specify a method attribute to switch from the default GET request to a POST request: <form method="POST">.
Even though GET and POST are not the only methods available in HTTP, they are the only two methods available on HTML forms. If you try anything other than POST, the browser will just do a GET instead. If you'd like to learn more about this, watch the tip below:
When the method is POST the form body is submitted as a "payload" instead of a query string. The browser will encode it as application/x-www-form-urlencoded. On the server side, you can use the Request's formData method to get the form data as a FormData object:
const formData = await request.formData()
const sandwichType = formData.get('sandwichType')
You can also change the encoding type of the form by setting the enctype attribute. The only typical value for this attribute is multipart/form-data, which is used for file uploads.
Sometimes the server code you want to have handle the form submission is not at the same URL where the form appears. In this case, you can use the action attribute to specify a different URL:
<form action="/make-a-sandwich">
	<label>
		Type:
		<input name="sandwichType" />
	</label>
	<button type="submit">Create Sandwich</button>
</form>
This would make a request to /make-a-sandwich instead of /sandwiches.

The back button

The browser submits forms with full-page refreshes, and expects to get a response from the server that tells the browser what to do next. If the response is HTML, then the browser will simply render that HTML. However you should not do this for a successful form submission.
Have you ever seen those really annoying popups when hitting the back button that say "Confirm Form Resubmission"? That's because the browser is trying to resubmit the form data to the server that you submitted when you were brought to that page the first time. This can be a major problem if the request that was submitted was a bank transfer or an airline ticket purchase.
This is a really common problem with an extremely simple solution: don't respond with HTML for successful form submissions. Instead, respond with a redirect (using the Location header and a 302 or 303 status code). This is commonly referred to as "Post/Redirect/Get" (PRG) pattern.

Revalidation

Because this is a full-page refresh, the HTML the server sends back to the user will always have the latest data. This is awesome because it means you don't really need to worry about state management. The database can be the source of truth. So with the foundational web primitives we have, web apps will always have fresh data as the user navigates with links and submits forms.
Unfortunately, that's not the default situation if you try to update data with JavaScript (which you can do with the fetch API). You would do this if you don't want a full-page refresh when the user submits a form, which is the user experience that our users expect from modern web applications. This drastically complicates things because now we have to worry about state management in the client to keep the UI up-to-date. But it's worth the work. Can you imagine every time you like a tweet you get a full-page refresh? What a disaster that would be!
This can be a real challenge... Unless you're using a Progressively Enhanced Single Page App framework like Remix ๐Ÿ˜‰

In Remix

Remix has mutation capabilities built-in the framework and it's entirely based on the browser behavior for <form>s. Remix is a "browser emulator" which simply means it prevents the full-page refresh, but still makes it so you don't have to worry about revalidation because Remix will revalidate all your data when the form submission is successful.
The API for mutations in your UI code is just like a regular <form>, except you use Remix's <Form> component.
Because Remix is a full-stack application framework, it also has a server-side API for handling the form's submission. It looks very similar to the loader API. In your route module, you export an async action function and receive the request and params, and you're expected to return a Response (remember, if the form submission is successful, you should return a redirect):
import { Form } from '@remix-run/react'
import { type DataFunctionArgs, redirect } from '@remix-run/node'

export async function action({ request, params }: DataFunctionArgs) {
	const formData = await request.formData()
	const sandwichType = formData.get('sandwichType')

	// do something with the sandwichType

	return redirect('/sandwiches')
}

export default function SandwichChooser() {
	return (
		<Form method="POST">
			<label>
				Type: <input name="sandwichType" />
			</label>
			<button type="submit">Create Sandwich</button>
		</Form>
	)
}
actions are called for non-GET requests, and loaders are called for GET request, so if your form does not specify a method or specifies method="GET", then your loader will be called instead of the action.
Again, once your form has been submitted, Remix will revalidate all your data so you don't have to worry about state management in the client.
It's important to know that on the web and in Remix, there can only be one "navigational" form submission at a time. You definitely can submit multiple forms back-to-back, but the last one will always "win" in the event of a redirect.
In Remix there's also a mechanism for programmatically submitting data without user interaction (using a hook called useFetcher). This is a bit more advanced than we'll get in this workshop though so we'll save it for later.