diff --git a/apps/svelte.dev/content/docs/kit/20-core-concepts/60-remote-functions.md b/apps/svelte.dev/content/docs/kit/20-core-concepts/60-remote-functions.md index fa00cf03a6..f0e26ff52e 100644 --- a/apps/svelte.dev/content/docs/kit/20-core-concepts/60-remote-functions.md +++ b/apps/svelte.dev/content/docs/kit/20-core-concepts/60-remote-functions.md @@ -230,7 +230,6 @@ export const getWeather = query.batch(v.string(), async (cities) => { The `form` function makes it easy to write data to the server. It takes a callback that receives `data` constructed from the submitted [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)... - ```ts /// file: src/routes/blog/data.remote.js // @filename: ambient.d.ts @@ -294,115 +293,188 @@ export const createPost = form(

Create a new post

+
+ + + +
+``` + +The form object contains `method` and `action` properties that allow it to work without JavaScript (i.e. it submits data and reloads the page). It also has an [attachment](/docs/svelte/@attach) that progressively enhances the form when JavaScript is available, submitting data *without* reloading the entire page. + +As with `query`, if the callback uses the submitted `data`, it should be [validated](#query-Query-arguments) by passing a [Standard Schema](https://standardschema.dev) as the first argument to `form`. + +### Fields + +A form is composed of a set of _fields_, which are defined by the schema. In the case of `createPost`, we have two fields, `title` and `content`, which are both strings. To get the attributes for a field, call its `.as(...)` method, specifying which [input type](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#input_types) to use: + +```svelte
``` -As with `query`, if the callback uses the submitted `data`, it should be [validated](#query-Query-arguments) by passing a [Standard Schema](https://standardschema.dev) as the first argument to `form`. The one difference is to `query` is that the schema inputs must all be of type `string` or `File`, since that's all the original `FormData` provides. You can however coerce the value into a different type — how to do that depends on the validation library you use. +These attributes allow SvelteKit to set the correct input type, set a `name` that is used to construct the `data` passed to the handler, populate the `value` of the form (for example following a failed submission, to save the user having to re-enter everything), and set the [`aria-invalid`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-invalid) state. -```ts -/// file: src/routes/count.remote.js +Fields can be nested in objects and arrays, and their values can be strings, numbers, booleans or `File` objects. For example, if your schema looked like this... + +```js +/// file: data.remote.js import * as v from 'valibot'; import { form } from '$app/server'; - -export const setCount = form( - v.object({ - // Valibot: - count: v.pipe(v.string(), v.transform((s) => Number(s)), v.number()), - // Zod: - // count: z.coerce.number() +// ---cut--- +const datingProfile = v.object({ + name: v.string(), + photo: v.file(), + info: v.object({ + height: v.number(), + likesDogs: v.optional(v.boolean(), false) }), - async ({ count }) => { - // ... - } -); + attributes: v.array(v.string()) +}); + +export const createProfile = form(datingProfile, (data) => { /* ... */ }); ``` -The `name` attributes on the form controls must correspond to the properties of the schema — `title` and `content` in this case. If you schema contains objects, use object notation: +...your form could look like this: ```svelte - - - -{#each jobs as job, idx} - - -{/each} + + +
+ + + + + + + + +

My best attributes

+ + + + + +
``` -To indicate a repeated field, use a `[]` suffix: +Because our form contains a `file` input, we've added an `enctype="multipart/form-data"` attribute. The values for `info.height` and `info.likesDogs` are coerced to a number and a boolean respectively. + +> [!NOTE] If a `checkbox` input is unchecked, the value is not included in the [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) object that SvelteKit constructs the data from. As such, we have to make the value optional in our schema. In Valibot that means using `v.optional(v.boolean(), false)` instead of just `v.boolean()`, whereas in Zod it would mean using `z.coerce.boolean()`. + +In the case of `radio` and `checkbox` inputs that all belong to the same field, the `value` must be specified as a second argument to `.as(...)`: + +```js +/// file: data.remote.js +import * as v from 'valibot'; +import { form } from '$app/server'; +// ---cut--- +export const survey = form( + v.object({ + operatingSystem: v.picklist(['windows', 'mac', 'linux']), + languages: v.optional(v.array(v.picklist(['html', 'css', 'js'])), []) + }), + (data) => { /* ... */ } +); +``` ```svelte - - - +
+

Which operating system do you use?

+ + {#each ['windows', 'mac', 'linux'] as os} + + {/each} + +

Which languages do you write code in?

+ + {#each ['html', 'css', 'js'] as language} + + {/each} + + +
``` -If you'd like type safety and autocomplete when setting `name` attributes, use the form object's `field` method: +Alternatively, you could use `select` and `select multiple`: ```svelte - -``` +
+

Which operating system do you use?

-This will error during typechecking if `title` does not exist on your schema. + -The form object contains `method` and `action` properties that allow it to work without JavaScript (i.e. it submits data and reloads the page). It also has an [attachment](/docs/svelte/@attach) that progressively enhances the form when JavaScript is available, submitting data *without* reloading the entire page. +

Which languages do you write code in?

+ + + + +
+``` + +> [!NOTE] As with unchecked `checkbox` inputs, if no selections are made then the data will be `undefined`. For this reason, the `languages` field uses `v.optional(v.array(...), [])` rather than just `v.array(...)`. ### Validation -If the submitted data doesn't pass the schema, the callback will not run. Instead, the form object's `issues` object will be populated: +If the submitted data doesn't pass the schema, the callback will not run. Instead, each invalid field's `issues()` method will return an array of `{ message: string }` objects, and the `aria-invalid` attribute (returned from `as(...)`) will be set to `true`: ```svelte
@@ -419,7 +491,7 @@ You don't need to wait until the form is submitted to validate the data — you By default, issues will be ignored if they belong to form controls that haven't yet been interacted with. To validate _all_ inputs, call `validate({ includeUntouched: true })`. -For client-side validation, you can specify a _preflight_ schema which will populate `issues` and prevent data being sent to the server if the data doesn't validate: +For client-side validation, you can specify a _preflight_ schema which will populate `issues()` and prevent data being sent to the server if the data doesn't validate: ```svelte +``` + ### Handling sensitive data -In the case of a non-progressively-enhanced form submission (i.e. where JavaScript is unavailable, for whatever reason) `input` is also populated if the submitted data is invalid, so that the user does not need to fill the entire form out from scratch. +In the case of a non-progressively-enhanced form submission (i.e. where JavaScript is unavailable, for whatever reason) `value()` is also populated if the submitted data is invalid, so that the user does not need to fill the entire form out from scratch. You can prevent sensitive data (such as passwords and credit card numbers) from being sent back to the user by using a name with a leading underscore: @@ -466,20 +566,12 @@ You can prevent sensitive data (such as passwords and credit card numbers) from @@ -681,12 +773,12 @@ This attribute exists on the `buttonProps` property of a form object: @@ -1003,7 +1095,10 @@ const getUser = query(() => { }); ``` -Note that some properties of `RequestEvent` are different inside remote functions. There are no `params` or `route.id`, and you cannot set headers (other than writing cookies, and then only inside `form` and `command` functions), and `url.pathname` is always `/` (since the path that’s actually being requested by the client is purely an implementation detail). +Note that some properties of `RequestEvent` are different inside remote functions: + +- you cannot set headers (other than writing cookies, and then only inside `form` and `command` functions) +- `route`, `params` and `url` relate to the page the remote function was called from, _not_ the URL of the endpoint SvelteKit creates for the remote function. Queries are not re-run when the user navigates (unless the argument to the query changes as a result of navigation), and so you should be mindful of how you use these values. In particular, never use them to determine whether or not a user is authorized to access certain data. ## Redirects diff --git a/apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md b/apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md index c86a0c2ae6..97db143f0e 100644 --- a/apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md +++ b/apps/svelte.dev/content/docs/kit/98-reference/10-@sveltejs-kit.md @@ -2331,20 +2331,6 @@ type RemoteForm< for( key: string | number | boolean ): Omit, 'for'>; - /** - * This method exists to allow you to typecheck `name` attributes. It returns its argument - * @example - * ```svelte - * - * ``` - **/ - field< - Name extends keyof UnionToIntersection< - FlattenKeys - > - >( - string: Name - ): Name; /** Preflight checks */ preflight( schema: StandardSchemaV1 @@ -2359,14 +2345,10 @@ type RemoteForm< get result(): Output | undefined; /** The number of pending submissions */ get pending(): number; - /** The submitted values */ - input: null | UnionToIntersection< - FlattenInput - >; - /** Validation issues */ - issues: null | UnionToIntersection< - FlattenIssues - >; + /** Access form fields using object notation */ + fields: Input extends void + ? never + : RemoteFormFields; /** Spread this onto a `