You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Today, to do a mutation, you typically need to create a server api route (express, or otherwise) and wire it all up with a bunch of JavaScript (that most of us do a poor job of cobbling together).
We've had multiple people already trying to use loaders as mutation endpoints, and we ourselves would like to as well.
A strong philosophy in Remix is that you should be able to build websites the "web 1.0 way". When it comes to mutations in HTML, the name of the game is <form method=post>. The developer experience with HTML forms for mutations is actually really great.
The end. It was so nice. No screwing around serializing the form, no loading states, or useEffect, or dealing with asynchronous APIs like fetch. Just make a form, the browser serializes the form, handles the asynchrony, and the server redirects to the new page. It's even a built-in state machine where the URLs are the states, which drastically simplifies the code you write.
Additionally, many projects have "wizard" like flows to create things, moving through routes makes a lot of sense but you can't really animate that, or there's state in the UI that you don't want to lose in a browser navigation.
This kind of stuff is typically not possible with web 1.0 style posts.
Why not both?
Remix is positioned to allow for both the superior developer experience of writing mutations as forms (the way HTML and HTTP are designed) while also enabling great user experiences like stripe checkout.
Currently, you can post to a Remix loader if you set up your server that way. For example, in express you can do app.all("*", createRequestHandler()). However, it pretty much only works if you're submit plain HTML forms w/o client side navigation.
With a new <Form/> component, we can add support for mutations on route loaders without doing full page reloads so that you can preserve state on the page for improved UX.
This means error handling, pending states, etc. are all normal transition states of Remix, but now for form mutations.
import{redirect}from"@remix-run/loader";import{createProject}from"../models/project";exportdefaultfunctionasyncLoader({context: { req }}){let[project,errors]=awaitcreateProject(body);if(project){returnredirect(`/projects/${project.id}`);}elseif(errors){return{ errors, body };}};
Remix Form does a fetch(..., { method: "post" }) to the loader, the loader creates a record and redirects, or it returns the errors and body to the component--which happens to be the very same route thats already rendering. Because this is all React, these are all just state changes inside of the components already rendered.
That means you can use isPending to do loading effects on the button, or even animate in the errors.
But the developer experience is identical to plain HTML forms! In fact, you could even disable JavaScript and it this would all still work. I guess that's what they meant by "progressive enhancement" 🤪
Form is like Link
React Router can't really ship with a <Form> component because it doesn't know about a server. Since Remix is a server, we can complete the browser navigation picture with client side routing. <Link> for get, <Form> for mutations 🎉
Implementation Notes
<Form> could navigate(to, { state: { method, body }}) to trigger @remix-run/react.fetchData to use the proper method.
could instead make a remix specific navigate like navigate(to, { method, body }) or navigate.post(to, body), just some ideas.
If we want to support put and delete, can use a <input type="hidden" name="__method" value="put"> since HTML forms don't support it.
Might be a few other things, but in general I think that's all we need.
Next level: turn loaders into http controllers
If we wanted to take this a step further, each deployment wrapper (express, aws, etc.) could add a body parser, and do method branching on the loader, so loaders could end up having an interface of exporting get, post, put, ad delete methods:
I'm wondering about what happens with more complicated mutations involving arrays or nested data structures. I guess this could be acheived using some kind of extended urlencoded syntax such as parsed by the qs module.
Today, to do a mutation, you typically need to create a server api route (express, or otherwise) and wire it all up with a bunch of JavaScript (that most of us do a poor job of cobbling together).
We've had multiple people already trying to use loaders as mutation endpoints, and we ourselves would like to as well.
A strong philosophy in Remix is that you should be able to build websites the "web 1.0 way". When it comes to mutations in HTML, the name of the game is
<form method=post>
. The developer experience with HTML forms for mutations is actually really great.Web 1.0 Mutations
The end. It was so nice. No screwing around serializing the form, no loading states, or useEffect, or dealing with asynchronous APIs like fetch. Just make a form, the browser serializes the form, handles the asynchrony, and the server redirects to the new page. It's even a built-in state machine where the URLs are the states, which drastically simplifies the code you write.
Why we do it client side
If you go to https://remix.run/newsletter you can see our submit button animates between states and just feels really fun. Stripe checkout has wonderful loading/error states as well.
Additionally, many projects have "wizard" like flows to create things, moving through routes makes a lot of sense but you can't really animate that, or there's state in the UI that you don't want to lose in a browser navigation.
This kind of stuff is typically not possible with web 1.0 style posts.
Why not both?
Remix is positioned to allow for both the superior developer experience of writing mutations as forms (the way HTML and HTTP are designed) while also enabling great user experiences like stripe checkout.
Currently, you can post to a Remix loader if you set up your server that way. For example, in express you can do
app.all("*", createRequestHandler())
. However, it pretty much only works if you're submit plain HTML forms w/o client side navigation.With a new
<Form/>
component, we can add support for mutations on route loaders without doing full page reloads so that you can preserve state on the page for improved UX.This means error handling, pending states, etc. are all normal transition states of Remix, but now for form mutations.
Big example:
The component:
The loader:
Remix
Form
does afetch(..., { method: "post" })
to the loader, the loader creates a record and redirects, or it returns the errors and body to the component--which happens to be the very same route thats already rendering. Because this is all React, these are all just state changes inside of the components already rendered.That means you can use
isPending
to do loading effects on the button, or even animate in the errors.But the developer experience is identical to plain HTML forms! In fact, you could even disable JavaScript and it this would all still work. I guess that's what they meant by "progressive enhancement" 🤪
Form is like Link
React Router can't really ship with a
<Form>
component because it doesn't know about a server. Since Remix is a server, we can complete the browser navigation picture with client side routing.<Link>
for get,<Form>
for mutations 🎉Implementation Notes
<Form>
couldnavigate(to, { state: { method, body }})
to trigger@remix-run/react.fetchData
to use the proper method.navigate(to, { method, body })
ornavigate.post(to, body)
, just some ideas.put
anddelete
, can use a<input type="hidden" name="__method" value="put">
since HTML forms don't support it.Might be a few other things, but in general I think that's all we need.
Next level: turn loaders into http controllers
If we wanted to take this a step further, each deployment wrapper (express, aws, etc.) could add a body parser, and do method branching on the loader, so loaders could end up having an interface of exporting
get
,post
,put
, addelete
methods:Can wait on this controller stuff though, all we need right now is
<Form>
andnavigate(to, { state: { method, body }})
The text was updated successfully, but these errors were encountered: