Form actions is not new concept, they’ve been here for a while. React introduced them in early 2024 meanwhile svelte and solid had these long before (no shade). Recently i’ve been working with svelte and i found a funny way to deal with forms as soon as i started using them. So let’s see the code how form actions are handled in svelte.
The official docs introduce you to form action very simply, here how they do:
<form method="POST" action="?/login">
<label>
Email
<input name="email" type="email">
</label>
<label>
Password
<input name="password" type="password">
</label>
<button>Log in</button>
<button formaction="?/register">Register</button>
</form>
export const load: PageServerLoad = async ({ cookies }) => {
const user = await db.getUserFromSession(cookies.get('sessionid'));
return { user };
};
export const actions = {
login: async ({ cookies, request }) => {
const data = await request.formData();
const email = data.get('email');
const password = data.get('password');
const user = await db.getUser(email);
cookies.set('sessionid', await db.createSession(user), { path: '/' });
return { success: true };
},
register: async (event) => {
// TODO register the user
}
} satisfies Actions;
if you try these out, it works. But do they work in real world application?
Let’s say you need to redirect the user to the profile page after logging in. How would you do it? You can just redirect them using svelte kit primitive! So let’s just add one line, Nice and easy.
throw redirect(307, "signin")
Now since this is a real world application we need to add try catch block so we can send optimistic update to the user, some sort of message that tells if user login was successful or not. How would you do that?
Svelte has something called ‘use:enhance’ action that make forms even more powerful. If you add this, to the form they’ll work even if the javascript is disabled in the browser!! So let’s add them!
<form method="POST" action="?/login" use:enhance>
What more you can do with this enhance is deliver optimistic update to the user. So let’s how we will do that:
<form method="POST" use:enhance={() => {
return async({result}) => {
if (result.type === "redirect"){
goto(result.location)
alert('successfully logged in')
}
if (result.type === 'error'){
alert('Wrong credentials')
}
}
}}>
<!-- Input fields -->
<!-- Submit button -->
</form>
I think this is pretty straight forward. you submit a form, whatever returns you check its type, if its redirect then you redirect and tell user that login was successful else wrong credentials.
Now let’s add try and catch to our action so they return proper types for it to work.
export const load: PageServerLoad = async ({ cookies }) => {
const user = await db.getUserFromSession(cookies.get('sessionid'));
return { user };
};
export const actions = {
login: async ({ cookies, request }) => {
try{
// login logic
if(loggedin){
throw redirect(303, '/profile')
}
}catch(e){
return {error: JSON.stringify(e) }
}
},
register: async (event) => {
// TODO register the user
} satisfies Actions;
Did it work? Nope? Okay now let’s try adding console log in the catch block and see what’s the error.
here’s what we see in the console:
Redirect { status: 303, location: '/profile' }
So the action is working why the page isn’t redirecting? And here’s the catch (pun intended)
When you throw a redirect inside a try catch block, it treats it as an actual error-thrown. Thus catch block catches it and the page is never actually redirected! So how get around this issue. I tried to find a solution but i ended up doing it on my own.
Svelte has a helper function isRedirect() which checks wheather its a redirect thrown by a redirect function. So we will use this inside the catch block to handle the thrown error and discard any other errors thrown.
catch (e) {
if (isRedirect(e)) {
throw redirect(e.status, e.location)
} else {
return { error: JSON.stringify(e) }
}
}
Now your redirects should work and your form action behaves as its intended.