Action Functions #9020
Replies: 11 comments 22 replies
-
Good news - we have most of the work for this done/reverted in remix-run/react-router#10413 |
Beta Was this translation helpful? Give feedback.
-
What should happen if I use this to run some client side code and then I call useSubmit? Since I have no way to await that submit() or fetcher.submit() what would be the pending state? |
Beta Was this translation helpful? Give feedback.
-
This is good news 🎉🎉🙌 |
Beta Was this translation helpful? Give feedback.
-
Another question, will the And related to that, what will happen before JS loads? Since now the form will need client-side JS to work, maybe this should be added only on |
Beta Was this translation helpful? Give feedback.
-
Disclaimer: I don't even use Remix in production (yet), but I've been learning it a lot and loving even much the fact that server entrypoints are all registered at route level. Would we really get only the |
Beta Was this translation helpful? Give feedback.
-
Will any loaders be called after the action has run? |
Beta Was this translation helpful? Give feedback.
-
While this seems a great dev experience, my only concern would be how it abides to the Remix philosophy of using web standards as much as possible… |
Beta Was this translation helpful? Give feedback.
-
I think it's a good approach for Client based actions. For server actions (future) I think the url approach is much better when it comes to a few aspects, but the main one is obviously scope. What Next is doing and React is going for lately is incoherent with language behavior. I personally have a lot of trouble explaining the boundary between server and client to junior devs in the new approach used ie. in Next. Remix is much simpler. This function is a server one, this is a client one. They are separated with files now even (thanks to new Vite bundler and Onboarding new devs to our Remix variant of app is so much easier. It takes around 1/4th of the time it took us in Next. Also good patterns like dependency injection and separation of concerns are very simple to explain and implement. Testing is also straightforward and we're not forced to do e2e testing everywhere. All of that thanks to the fact that there is a clear (url) boundary between those two contexts. I remember a few years back the big reason for moving away from class components and why it was happening was that users could not understand what binding variables was. So besides the rant above :D, I'm wondering if this proposal will do any context reconstructing for client functions? ie.
The
|
Beta Was this translation helpful? Give feedback.
-
What happens if the route module also exports a |
Beta Was this translation helpful? Give feedback.
-
Can be an API similar to export const loader = () => {
return axios.get("/islogin");
};
export default () => {
const { data: islogin } = useLoaderData<typeof loader>();
const { isLoading, mutateAsync } = useAction(({ username, password }) => {
return axios.post("/login", { username, password });
});
if (islogin) {
return <div>You are already logged in</div>;
}
// use Form
return (
<Spin loading={isLoading}>
<Form
action={async (formData) => {
try {
// mutateAsync will auto revalidate loader data if the action promise resolved
const res = await mutateAsync(formData);
if (res.data) {
toast.success("login successful");
}
} catch (err) {
console.error(err);
toast.error("wrong username or password");
}
}}
>
{/* ... */}
</Form>
</Spin>
);
// or not use Form
return (
<Button
loading={isLoading}
onClick={async () => {
try {
// mutateAsync will auto revalidate loader data if the action promise resolved
// username and password not from formData but something like useState
const res = await mutateAsync({ username, password });
if (res.data) {
toast.success("login successful");
}
} catch (err) {
console.error(err);
toast.error("wrong username or password");
}
}}
>
login
</Button>
);
}; |
Beta Was this translation helpful? Give feedback.
-
I now expect remix-run/react-router#11564 to remove the need for this, closing for now. If I'm wrong we can revisit this. |
Beta Was this translation helpful? Give feedback.
-
tl;dr: allow actions to
forms
andsubmit
to be client side functions that trigger the usual pending statesToday actions are configured routes called indirectly through URLs (or the closes route action with an undefined action)
In the case the action is undefined, it uses the closest route in context.
Problems
This is mostly fine and matches up with traditional web development, both old-school PHP and SPAs. There was always a route backing a form submission, whether it was another PHP file you posted to or in a SPA, a REST endpoint you called.
Remix took both of those ideas and smooshed it together into one idea, removing a bunch of client/server boilerplate.
Indirection
However, there's a level of indirection that can make code harder to track. Seeing a
<Form method="post">
just in the wild, you have to figure out where it's rendered in order to know which action is being called.Silliness
For React Router and Remix SPA actions, it's kinda silly that you have to go through a URL to call a function that's right there, sometimes in the same file. It made a lot of sense for Remix in the beginning where a server was always assumed (and therefore indirection was inherent), but now that we have client-side actions, it's kinda weird.
Future RPC
When we first saw SolidStart's
createAction$
we discussed adding it to Remix, but hesitated because it's enabled by some "bundler tricks" that we weren't comfortable with (we weren't doing any AST parsing yet, and as a principle didn't really want to start).But the DX of the approach is clearly great.
Fast forward, and React has added very Remix like features with "server actions" that are very RPC-like as well with
"use server"
(and SolidStart even use"use server"
now too). The future is clearly RPC-like actions to add a better DX to calling actions on the server.In other words,
"use server"
automatically creates the action URL for you, connects the action directly to the component, removing indirection in the code.So, adding all of this together, the awkwardness of indirection through a URL for client actions, and the future RPC actions, I'd like to propose we get started on this path with a small change.
Proposal
Anywhere an action takes a URL today, it can also accept a client function (no RPC yet).
Remix router will go through its normal pending states as it waits for the action to finish.
This is probably most useful on fetchers
Benefits
Migration to Remix:
This will make migrating React Router SPAs to Remix SPAs much easier. Most existing React data code is scoped inside of components and hoisting it out to
clientAction
is not straightforward and not always a clear win.One major issue is how many integrations rely on React Context, for example,
useElements()
from stripe is very difficult to get to work with a clientAction since the stripe hooks and components rely on the same context butclientAction
isn't in the render tree.Removed indirection:
It's easier to see what code is going to be called when a form is submitted
Future RPC:
This skips a step for React Router SPAs path to RSC and server actions. Instead of going from:
They can go straight from inline data actions to "use server" actions when Remix supports RPC actions.
Benefits for Remix SSR
This still benefits Remix SSR apps too and puts people in control of calling their server actions manually if they'd like, while Remix still goes into all of it's pending states as they do:
Future
In the future, Remix can implement
"use server"
and allow people to call server actions directly from their components, skipping the URL indirection and making the code easier to follow.Now whether it's a client action or a server action, you wire it up to a form directly.
Beta Was this translation helpful? Give feedback.
All reactions