Sveltekit Changes: Form Actions and Progressive Enhancement
Tags: javascript, webdev, svelte, sveltekit
Published: 2022-09-18 18:36:30

## Introduction

In this article we gonna learn and talk about SvelteKit form progressive enhancement use action and new form actions. Now in SvelteKit you don't have to worry about form submission handling or how to handle our apps if Javascript is disabled in users browsers. There are a lot of problems related to forms and SvelteKit tried to fix some of them and I think they did the right. 

Here we mainly going to focus on `use:enhance(Progressive Enhancement)` and `form actions`.Progressive Enhancement includes form response handling, applyResults to client and things related to client. Form actions are mainly for server side thing when you submit a form `Form actions` comes to play in backend(server side). It includes default actions, named actions, error and redirects.

> We are going to start with Form Actions.

## Form Actions

Form action is new way to handle forms submitted from client side with post request on server side. This is all going to take place inside `+page.server.ts` or `+page.server.js` which exports a function by the name `actions`. Let me show you with example of `default actions`.

- Default Actions: 
```Javascript 
// src/routes/login/+page.server.js

/** @type {import('./$types').Actions} */
export const actions = {
  default: async ({ request }) => {
     const form = await request.formData();
     const email = form.get('email');
     const password = form.get('password');
     // do whatever you want to do with email and password
  }
};

// src/routes/login/+page.svelte

<form method="POST">
  <input name="email" type="email">
  <input name="password" type="password">
  <button>Log In</button>
</form>
```

As you can see in `+page.svelte` we made a form with post request to submit to server and on `+page.server.js` we have a exported const `actions`. This action have default parameter which which is async function and we can get all data from request. Here, If user clicks on `Log in` button for will be submitted using POST method using Default Action.

Sometimes, We have a form somewhere in the layout and layout doesn’t have Actions to handle form submission using Action. In these cases, we can have a route to that page and we can use that route as `action=/url`. 

For example we have a login form in navbar which is in layout and we need to submit the form using Actions. So here we gonna make a `login` route (includes action in `+page.server.js` which default action) and in form we gonna pass that route.
```html
// src/routes/+layout.svelte

<form method="POST" action="/login">
  <!-- content -->
</form>
```
> When using `<form>`, client-side JavaScript is optional, but you can easily progressively enhance your form interactions with JavaScript to provide the best user experience.

- Named Layouts

As we know we might have multiple forms on same page and we need to submit them using actions and default action can we used in one form. Here we gonna take an example if we have `register` and `login` forms on same route and we need to submit both. Here, Named routes comes to play these works like default but with a name like me and you.

```Javascript 
// src/routes/user/+page.server.js

/** @type {import('./$types').Actions} */
export const actions = {
  login: async (event) => {
    // TODO log the user in
  },
  register: async (event) => {
   // TODO register the user
  }
};

// src/routes/user/+page.svelte
<form method="POST" action="?/login">
  <!-- content -->
</form>

<form method="POST" action="?/register">
  <!-- content -->
</form>
```
As you can see in `+page.server.js` we have defined are actions const and inside that we named our functions `login` and `register`. 

Here, we don't have a default action so how we gonna call them on our form. So basically on forms we can pas a `action` parameter with the specific named action. As in above code block on both forms i passed `action="?/login"` for login named action and `action="?/register"` for register named action. Here `?/` these things something you have seen in URL's where `?` means a parameter is passed on to request and `/login` or `/register` is the parameters for the actions to be called on server.

 As we discussed in `default action` for form to be in `layout` page then what we can do in named actions is:

```html
<form method="POST" action="/user?/register">
</form>
```
 We just have to add route with parameters of named layout.

- Error handling in Actions

When we add checks for user input and find error we need to return a response so client/user should know what mistake they have made. 

```Javascript 
import { invalid } from '@sveltejs/kit';

/** @type {import('./$types').Actions} */
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);
		if (!user) {
			return invalid(400, { email, missing: true });
		}

		if (user.password !== hash(password)) {
			return invalid(400, { email, incorrect: true });
		}

    cookies.set('sessionid', await db.createSession(user));

    return { success: true };
  }};

// html
<script>
  /** @type {import('./$types').PageData} */
  export let data;
  /** @type {import('./$types').ActionData} */
  export let form;
</script>

{#if form?.success}
  <!-- this message is ephemeral; it exists because the page was rendered in
       response to a form submission. it will vanish if the user reloads -->
  <p>Successfully logged in! Welcome back, {data.user.name}</p>
{/if}
```
SvelteKit Provides a `invlaid` function which handle all the error related issues. You just need to pass two parameters to it. Ist is status code of error and IInd is data and it can be anything you need on client side for error handling.

In above code snippet, You can see I'm checking for user exist in db if not I'm returning `return invalid(400, { email, missing: true })`. That's all we need to handle error in actions. If you have no errors you can just simply return a dictionary just like `return { success: true };`. This will do the job for errors and success return.

All the errors and success data we return from actions can be accessed using  `export let form;` and using that you can provide users with more interactive UI/UX.

- Redirects in Actions

```Javascript 
// @errors: 2339 2304
import { invalid, redirect } from '@sveltejs/kit';

/** @type {import('./$types').Actions} */
export const actions = {
	login: async ({ cookies, request, url }) => {
    const data = await request.formData();
    const email = data.get('email');
    const password = data.get('password');

    const user = await db.getUser(email);
    if (!user) {
      return invalid(400, { email, missing: true });
    }

    if (user.password !== hash(password)) {
      return invalid(400, { email, incorrect: true });
    }

    cookies.set('sessionid', await db.createSession(user));

		if (url.searchParams.has('redirectTo')) {
			throw redirect(303, url.searchParams.get('redirectTo'));
		}

    return { success: true };
  }};
```

In above code snippet,We are looking for a redirect parameter so we can redirect to specified location.

```Javascript 
if (url.searchParams.has('redirectTo')) {
			throw redirect(303, url.searchParams.get('redirectTo'));
		}
```

But what is important here is `throw redirect(303, url.searchParams.get('redirectTo'))`. As you can see `redirect` function which is responsible for redirecting to a route takes two parameters one is `status code` for redirect and second is `location` to be redirected. For example if user is authenticated successfully we need to redirect user to `dashboard`. So, we can just do 
```Javascript 
throw redirect(303, '/dashboard')
```
This will redirect user too `dashboard` page.

> Now we have completely covered Form Actions. We need to learn about `Progressive Enhancement`.

## Progressive Enhancement 

Above, we made a `/login` action that works without client-side JavaScript — not a fetch in sight. That's great, but when JavaScript is available we can progressively enhance our form interactions to provide a better user experience.

And we can achieve that just adding a single `use action` method from svelte. To progressively enhance a form is to add the `use:enhance` action:

```html
<script>
	import { enhance } from '$app/forms';

  /** @type {import('./$types').ActionData} */
  export let form;
</script>

<form method="POST" use:enhance>
```

Just add `use:enhance` and it will do all browser behaviored things without page reload.It will:

1. update the `form` property and invalidate all data on a successful response.
2. update the `form` property on a invalid response.
3. update `$page.status` on a successful or invalid response.
4. call `goto` on a redirect response.
5. render the nearest `+error` boundary if an error occurs.

> By default the form property is only updated for actions that are in a +page.server.js alongside the +page.svelte because in the native form submission case you would be redirected to the page the action is on.

To customise the behaviour, you can provide a function that runs immediately before the form is submitted, and (optionally) returns a callback that runs with the ActionResult.

```Javascript 
<form
  method="POST"
  use:enhance={({ form, data, cancel }) => {
    // `form` is the `<form>` element
    // `data` is its `FormData` object response from action
    // `cancel()` will prevent the submission

    return async ({ result }) => {
      // `result` is an `ActionResult` object
    };
  }}
>
```
This will help us for UI changes without reloading page on the basis of form state.

- applyAction Method

Sometimes we need to handle our own errors or redirects on client. Here, we have access to all data returned by the form actions in `+page.server.js`. 

```Javascript 
<script>
	import { enhance, applyAction } from '$app/forms';

  /** @type {import('./$types').ActionData} */
  export let form;
</script>

<form
  method="POST"
  use:enhance={({ form, data, cancel }) => {
    // `form` is the `<form>` element
    // `data` is its `FormData` object
    // `cancel()` will prevent the submission

    return async ({ result }) => {
      // `result` is an `ActionResult` object
			if (result.type === 'error') {
				await applyAction(result);
			}
    };
  }}
>
```

Here we are looking for `error` in `results` returned by the `form actions`. If `result.type === 'error'` we gonna use `applyAction(result)` on result to handle error to nearest `error` page. 

The behaviour of `applyAction(result)` depends on result.type:

- success, invalid — sets `$page.status` to `result.status` and updates form to `result.data`
- redirect — calls `goto(result.location)`
- error — renders the nearest `+error` boundary with `result.error`

- Custom event listener
Custom event listeners are same things with what we used to do with forms in sveltekit earlier. I'll hope that you will try to understand this code snippet.

```Javascript 
<script>
  import { invalidateAll, goto } from '$app/navigation';
  import { applyAction } from '$app/forms';
  /** @type {import('./$types').ActionData} */
  export let form;
  /** @type {any} */
  let error;

  async function handleSubmit(event) {
    const data = new FormData(this);

    const response = await fetch(this.action, {
      method: 'POST',
      body: data
    });
    /** @type {import('@sveltejs/kit').ActionResult} */
    const result = await response.json();

    if (result.type === 'success') {
      // re-run all `load` functions, following the successful update
      await invalidateAll();
    }

    applyAction(result);
  }
</script>

<form method="POST" on:submit|preventDefault={handleSubmit}>
  <!-- content -->
</form>
```

This is a straight forward onclick and preventDefault with custom submit.

These all things are making sveltekit much better than what it used to be and breaking changes might be headache but after v1 we are gonna enjoy it much more than we think.

This is me writing for you. If you wanna ask or suggest anything please put it in comment.