Skip to content

Commit 604361d

Browse files
authored
docs: add standard schema example to Vue (#1672)
1 parent 5808c88 commit 604361d

File tree

13 files changed

+285
-0
lines changed

13 files changed

+285
-0
lines changed

docs/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,10 @@
563563
{
564564
"label": "Arrays",
565565
"to": "framework/vue/examples/array"
566+
},
567+
{
568+
"label": "Standard Schema",
569+
"to": "framework/vue/examples/standard-schema"
566570
}
567571
]
568572
},
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
dist-ssr
5+
*.local
6+
7+
package-lock.json
8+
yarn.lock
9+
pnpm-lock.yaml
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Basic example
2+
3+
To run this example:
4+
5+
- `npm install` or `yarn` or `pnpm i`
6+
- `npm run dev` or `yarn dev` or `pnpm dev`
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>TanStack Form Vue Simple Example App</title>
7+
</head>
8+
<body>
9+
<div id="app"></div>
10+
<script type="module" src="/src/main.ts"></script>
11+
</body>
12+
</html>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@tanstack/form-example-vue-standard-schema",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vite build",
8+
"build:dev": "vite build -m development",
9+
"test:types": "vue-tsc",
10+
"serve": "vite preview"
11+
},
12+
"dependencies": {
13+
"@tanstack/vue-form": "^1.15.1",
14+
"arktype": "^2.1.20",
15+
"effect": "^3.16.7",
16+
"react": "^19.0.0",
17+
"react-dom": "^19.0.0",
18+
"valibot": "^1.1.0",
19+
"vue": "^3.5.13",
20+
"zod": "^3.25.64"
21+
},
22+
"devDependencies": {
23+
"@vitejs/plugin-vue": "^5.2.4",
24+
"typescript": "5.8.2",
25+
"vite": "^6.3.5",
26+
"vue-tsc": "^2.2.2"
27+
}
28+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<script setup lang="ts">
2+
import { useForm } from '@tanstack/vue-form'
3+
import { type } from 'arktype'
4+
import * as v from 'valibot'
5+
import { z } from 'zod'
6+
import { Schema as S } from 'effect'
7+
import FieldInfo from './FieldInfo.vue'
8+
9+
const ZodSchema = z.object({
10+
firstName: z
11+
.string()
12+
.min(3, '[Zod] You must have a length of at least 3')
13+
.startsWith('A', "[Zod] First name must start with 'A'"),
14+
lastName: z.string().min(3, '[Zod] You must have a length of at least 3'),
15+
})
16+
17+
const ValibotSchema = v.object({
18+
firstName: v.pipe(
19+
v.string(),
20+
v.minLength(3, '[Valibot] You must have a length of at least 3'),
21+
v.startsWith('A', "[Valibot] First name must start with 'A'"),
22+
),
23+
lastName: v.pipe(
24+
v.string(),
25+
v.minLength(3, '[Valibot] You must have a length of at least 3'),
26+
),
27+
})
28+
29+
const ArkTypeSchema = type({
30+
firstName: 'string >= 3',
31+
lastName: 'string >= 3',
32+
})
33+
34+
const EffectSchema = S.standardSchemaV1(
35+
S.Struct({
36+
firstName: S.String.pipe(
37+
S.minLength(3),
38+
S.annotations({
39+
message: () => '[Effect/Schema] You must have a length of at least 3',
40+
}),
41+
),
42+
lastName: S.String.pipe(
43+
S.minLength(3),
44+
S.annotations({
45+
message: () => '[Effect/Schema] You must have a length of at least 3',
46+
}),
47+
),
48+
}),
49+
)
50+
51+
const form = useForm({
52+
defaultValues: {
53+
firstName: '',
54+
lastName: '',
55+
},
56+
validators: {
57+
// DEMO: You can switch between schemas seamlessly
58+
onChange: ZodSchema,
59+
// onChange: ValibotSchema,
60+
// onChange: ArkTypeSchema,
61+
// onChange: EffectSchema,
62+
},
63+
onSubmit: async ({ value }) => {
64+
// Do something with form data
65+
alert(JSON.stringify(value))
66+
},
67+
})
68+
</script>
69+
70+
<template>
71+
<form
72+
@submit="
73+
(e) => {
74+
e.preventDefault()
75+
e.stopPropagation()
76+
form.handleSubmit()
77+
}
78+
"
79+
>
80+
<div>
81+
<form.Field name="firstName">
82+
<template v-slot="{ field, state }">
83+
<label :htmlFor="field.name">First Name:</label>
84+
<input
85+
:id="field.name"
86+
:name="field.name"
87+
:value="field.state.value"
88+
@input="
89+
(e) => field.handleChange((e.target as HTMLInputElement).value)
90+
"
91+
@blur="field.handleBlur"
92+
/>
93+
<FieldInfo :state="state" />
94+
</template>
95+
</form.Field>
96+
</div>
97+
<div>
98+
<form.Field name="lastName">
99+
<template v-slot="{ field, state }">
100+
<label :htmlFor="field.name">Last Name:</label>
101+
<input
102+
:id="field.name"
103+
:name="field.name"
104+
:value="field.state.value"
105+
@input="
106+
(e) => field.handleChange((e.target as HTMLInputElement).value)
107+
"
108+
@blur="field.handleBlur"
109+
/>
110+
<FieldInfo :state="state" />
111+
</template>
112+
</form.Field>
113+
</div>
114+
<form.Subscribe>
115+
<template v-slot="{ canSubmit, isSubmitting }">
116+
<button type="submit" :disabled="!canSubmit">
117+
{{ isSubmitting ? '...' : 'Submit' }}
118+
</button>
119+
</template>
120+
</form.Subscribe>
121+
</form>
122+
</template>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<script setup lang="ts">
2+
import type { AnyFieldApi } from '@tanstack/vue-form'
3+
4+
const props = defineProps<{
5+
state: AnyFieldApi['state']
6+
}>()
7+
</script>
8+
9+
<template>
10+
<template v-if="props.state.meta.isTouched">
11+
<em v-for="error of props.state.meta.errors">{{ error.message }}</em>
12+
{{ props.state.meta.isValidating ? 'Validating...' : null }}
13+
</template>
14+
</template>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { createApp } from 'vue'
2+
3+
import App from './App.vue'
4+
5+
createApp(App).mount('#app')
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare module '*.vue' {
2+
import { DefineComponent } from 'vue'
3+
const component: DefineComponent<{}, {}, any>
4+
export default component
5+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface Post {
2+
userId: number
3+
id: number
4+
title: string
5+
body: string
6+
}

0 commit comments

Comments
 (0)