Using Router Action with Solid (not SolidStart)

I'm trying to understand how to use Solid Router actions with Solid (the core library, not Solid Start). When I submit a form which has an action the browser redirects to https://action/.... So something is going wrong that I don't see. Here is a test I created in the Solid Playground, adapting the example from the docs: https://playground.solidjs.com/anonymous/09ea99f6-f72a-4f63-8c80-7e86cba1b348. As you can see when the form is submitted it redirects to a URL which doesn't exist.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
7 Replies
peerreynders
peerreynders2mo ago
Dropped into the basic template this works on StackBlitz:
// file: src/routes/about.tsx
import { Show } from 'solid-js';
import { action, redirect, useAction, useSubmission } from '@solidjs/router';
import { Title } from '@solidjs/meta';

const isAdmin = action(async (formData: FormData) => {
await new Promise((resolve, reject) => setTimeout(resolve, 1000));
const username = formData.get('username');

if (username === 'admin') throw redirect('/admin');
return new Error('Invalid username');
}, 'login');

function About() {
const submit = useAction(isAdmin);
const submission = useSubmission(isAdmin);
const submitListener = (e: Event & { currentTarget: HTMLFormElement }) => {
submission.clear?.();
submit(new FormData(e.currentTarget));
};

return (
<main>
<Title>About</Title>
<h1>About</h1>
<form action={isAdmin} method="post" onSubmit={submitListener}>
<label for="username">Username:</label>
<input type="text" name="username" />
<input type="submit" value="submit" />
</form>
<Show when={submission.result}>
{(error) => <p>{error().message}</p>}
</Show>
</main>
);
}

export { About };
// file: src/routes/about.tsx
import { Show } from 'solid-js';
import { action, redirect, useAction, useSubmission } from '@solidjs/router';
import { Title } from '@solidjs/meta';

const isAdmin = action(async (formData: FormData) => {
await new Promise((resolve, reject) => setTimeout(resolve, 1000));
const username = formData.get('username');

if (username === 'admin') throw redirect('/admin');
return new Error('Invalid username');
}, 'login');

function About() {
const submit = useAction(isAdmin);
const submission = useSubmission(isAdmin);
const submitListener = (e: Event & { currentTarget: HTMLFormElement }) => {
submission.clear?.();
submit(new FormData(e.currentTarget));
};

return (
<main>
<Title>About</Title>
<h1>About</h1>
<form action={isAdmin} method="post" onSubmit={submitListener}>
<label for="username">Username:</label>
<input type="text" name="username" />
<input type="submit" value="submit" />
</form>
<Show when={submission.result}>
{(error) => <p>{error().message}</p>}
</Show>
</main>
);
}

export { About };
minion_lover43
minion_lover432mo ago
you dont have a route component here you need to wrap eeryrhing in a Router and Route
import { render } from "solid-js/web";
import { action, redirect, Router, Route } from "@solidjs/router";

const isAdmin = action(async (formData: FormData) => {
console.log('running...')
await new Promise((resolve, reject) => setTimeout(resolve, 1000));

const username = formData.get("username");

if (username === "admin") throw redirect("/admin");
return new Error("Invalid username");
});
export function MyComponent() {
return (
<form action={isAdmin} method="post">
<label for="username">Username:</label>
<input type="text" name="username" />
<input type="submit" value="submit" />
</form>
);
}

function App() {
return (
<Router><Route path="*" component={MyComponent} /></Router>
);
}

render(() => <App />, document.getElementById("app")!);
import { render } from "solid-js/web";
import { action, redirect, Router, Route } from "@solidjs/router";

const isAdmin = action(async (formData: FormData) => {
console.log('running...')
await new Promise((resolve, reject) => setTimeout(resolve, 1000));

const username = formData.get("username");

if (username === "admin") throw redirect("/admin");
return new Error("Invalid username");
});
export function MyComponent() {
return (
<form action={isAdmin} method="post">
<label for="username">Username:</label>
<input type="text" name="username" />
<input type="submit" value="submit" />
</form>
);
}

function App() {
return (
<Router><Route path="*" component={MyComponent} /></Router>
);
}

render(() => <App />, document.getElementById("app")!);
this works for me, running is printed to the console i did path="*" because path="/" doesnt work for some reason, probably the sandbox path is used or something im trying to do something similar, what is the error param in the function in the show tag? my submission.result is currently not showing for my own app
peerreynders
peerreynders2mo ago
It's an accessor to the value returned by the expression in the when prop once the value isn't falsey. Because the Error is returned rather than thrown it appears on submission.result rather than submission.error once submission.pending transitions from true to false.
minion_lover43
minion_lover432mo ago
woah thats really useful i didnt know that thanks
eponymous
eponymous2mo ago
Thanks for your replies Ah, yes! That was a bad example. In my actual code I do have the component being rendered on a route. In my app I'm using component routing, which works fine, and the particular component with which I'm trying to use the action is rendered through the route, like this
<Router>
<Route path="/path" component={MyComponent} />
</Router>
<Router>
<Route path="/path" component={MyComponent} />
</Router>
So, it is wrapped in a route. Is there something else I need to do? P.S. For some reason I'm having trouble creating an example on StackBlitz, the route only renders a blank screen even when I use an inline component like Route path="/hello-world" component={<h1>Hello World!</h1>} />
peerreynders
peerreynders2mo ago
Here is the one I created https://stackblitz.com/edit/stackblitz-starters-zmfxxa?file=src%2Froutes%2Fabout.tsx Fork it if you like as I don't know how long I'll keep it. The basic one I always start with is https://stackblitz.com/edit/stackblitz-starters-bjbk1s?file=src%2Froutes%2Fabout.tsx
peerreynders
StackBlitz
solid-router Basic (forked) - StackBlitz
A Solid TypeScript project based on @solidjs/meta, @solidjs/router, solid-js, typescript, vite and vite-plugin-solid
peerreynders
StackBlitz
solid-router Basic - StackBlitz
A Solid TypeScript project based on @solidjs/meta, @solidjs/router, solid-js, typescript, vite and vite-plugin-solid
eponymous
eponymous2mo ago
I forked it. Thanks, this helps tremendously. @peerreynders What's the advantage of returning the error from the action vs throwing the error? It seems to me that it would be easier to just test if submission.error has a value, whereas you don't know if submission.result is returning an error value or a success value without an additional test. @peerreynders Nevermind. I see that you have spoken about this in one or two other discussions, and Ryan also chimed in on this recently. So in that case I'm going to just test if submission.result is an error and not use submission.error (though that doesn't feel intuitive, however throwing an error in the action doesn't feel intuitive either).
Want results from more Discord servers?
Add your server