Skip to content

Commit 8a6c804

Browse files
committed
Merge branch 'main' into vue-use-app-form
2 parents 973be05 + e669d93 commit 8a6c804

File tree

182 files changed

+10850
-6301
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

182 files changed

+10850
-6301
lines changed

README.md

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,34 @@
1-
<img src="https://static.scarf.sh/a.png?x-pxid=be2d8a11-9712-4c1d-9963-580b2d4fb133" />
1+
<img src="https://static.scarf.sh/a.png?x-pxid=be2d8a11-9712-4c1d-9963-580b2d4fb133" alt="" />
22

33
![TanStack Form Header](https://github.com/TanStack/form/raw/main/media/repo-header.png)
44

5-
Powerful and type-safe form state management for the web. TS/JS, React Form, Solid Form, Angular Form, Lit Form and Vue Form.
5+
Powerful and type-safe form state management for the web. TS/JS, React Form, Solid Form, Angular Form, Lit Form and Vue
6+
Form.
67

7-
<a href="https://twitter.com/intent/tweet?button_hashtag=TanStack" target="\_parent">
8-
<img alt="#TanStack" src="https://img.shields.io/twitter/url?color=%2308a0e9&label=%23TanStack&style=social&url=https%3A%2F%2Ftwitter.com%2Fintent%2Ftweet%3Fbutton_hashtag%3DTanStack">
9-
</a><a href="https://discord.com/invite/WrRKjPJ" target="\_parent">
10-
<img alt="" src="https://img.shields.io/badge/Discord-TanStack-%235865F2" />
11-
</a><a href="https://www.npmjs.com/package/@tanstack/form-core" target="\_parent">
12-
<img alt="" src="https://img.shields.io/npm/dm/@tanstack/form-core.svg" />
13-
</a><a href="https://bundlephobia.com/package/@tanstack/form-core@latest" target="\_parent">
14-
<img alt="" src="https://badgen.net/bundlephobia/minzip/@tanstack/form-core" />
8+
<a href="https://twitter.com/intent/tweet?button_hashtag=TanStack" target="_parent">
9+
<img alt="Tweet about TanStack with hashtag #TanStack" src="https://img.shields.io/twitter/url?color=%2308a0e9&label=%23TanStack&style=social&url=https%3A%2F%2Ftwitter.com%2Fintent%2Ftweet%3Fbutton_hashtag%3DTanStack">
10+
</a><a href="https://discord.com/invite/WrRKjPJ" target="_parent">
11+
<img alt="Join the TanStack Discord Community" src="https://img.shields.io/badge/Discord-TanStack-%235865F2" />
12+
</a><a href="https://www.npmjs.com/package/@tanstack/form-core" target="_parent">
13+
<img alt="NPM downloads for @tanstack/form-core" src="https://img.shields.io/npm/dm/@tanstack/form-core.svg" />
14+
</a><a href="https://bundlephobia.com/package/@tanstack/form-core@latest" target="_parent">
15+
<img alt="Minified + gzipped bundle size of @tanstack/form-core" src="https://badgen.net/bundlephobia/minzip/@tanstack/form-core" />
1516
</a><a href="#badge">
16-
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
17-
</a><a href="https://github.com/TanStack/form/discussions">
18-
<img alt="Join the discussion on Github" src="https://img.shields.io/badge/Github%20Discussions%20%26%20Support-Chat%20now!-blue" />
19-
</a><a href="https://bestofjs.org/projects/tanstack-form"><img alt="Best of JS" src="https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=TanStack%2Fform%26since=daily" /></a><a href="https://github.com/TanStack/form/" target="\_parent">
20-
<img alt="" src="https://img.shields.io/github/stars/TanStack/form.svg?style=social&label=Star" />
21-
</a><a href="https://twitter.com/tannerlinsley" target="\_parent">
22-
<img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
23-
</a> <a href="https://gitpod.io/from-referrer/">
24-
<img src="https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod" alt="Gitpod Ready-to-Code"/>
17+
<img alt="Semantic Release Enabled" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
18+
</a><a href="https://github.com/TanStack/form/discussions">
19+
<img alt="Join GitHub Discussions for TanStack Form" src="https://img.shields.io/badge/Github%20Discussions%20%26%20Support-Chat%20now!-blue" />
20+
</a><a href="https://bestofjs.org/projects/tanstack-form">
21+
<img alt="TanStack Form featured on Best of JS" src="https://img.shields.io/endpoint?url=https://bestofjs-serverless.now.sh/api/project-badge?fullName=TanStack%2Fform%26since=daily" />
22+
</a><a href="https://github.com/TanStack/form/" target="_parent">
23+
<img alt="Star TanStack Form on GitHub" src="https://img.shields.io/github/stars/TanStack/form.svg?style=social&label=Star" />
24+
</a><a href="https://twitter.com/tannerlinsley" target="_parent">
25+
<img alt="Follow Tanner Linsley on Twitter" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
26+
</a><a href="https://gitpod.io/from-referrer/">
27+
<img alt="Open TanStack Form in Gitpod" src="https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod" />
2528
</a>
2629

27-
Enjoy this library? Try the entire [TanStack](https://tanstack.com)! [TanStack Query](https://github.com/TanStack/query), [TanStack Table](https://github.com/TanStack/table), [TanStack Router](https://github.com/tanstack/router), [TanStack Virtual](https://github.com/tanstack/virtual), [React Charts](https://github.com/TanStack/react-charts), [React Ranger](https://github.com/TanStack/ranger)
30+
Enjoy this library? Try the
31+
entire [TanStack](https://tanstack.com)! [TanStack Query](https://github.com/TanStack/query), [TanStack Table](https://github.com/TanStack/table), [TanStack Router](https://github.com/tanstack/router), [TanStack Virtual](https://github.com/tanstack/virtual), [React Charts](https://github.com/TanStack/react-charts), [React Ranger](https://github.com/TanStack/ranger)
2832

2933
## Visit [tanstack.com/form](https://tanstack.com/form) for docs, guides, API and more!
3034

codecov.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
codecov:
2+
max_report_age: off
13
coverage:
24
status:
35
project:
99.6 KB
Loading

docs/assets/field-states.png

-107 KB
Loading

docs/config.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,10 @@
191191
{
192192
"label": "Arrays",
193193
"to": "framework/angular/guides/arrays"
194+
},
195+
{
196+
"label": "Form Composition",
197+
"to": "framework/angular/guides/form-composition"
194198
}
195199
]
196200
},
@@ -487,6 +491,10 @@
487491
{
488492
"label": "Balastrong's Tutorial",
489493
"to": "framework/react/community/balastrong-tutorial"
494+
},
495+
{
496+
"label": "Community Tutorials",
497+
"to": "framework/react/community/tutorials"
490498
}
491499
]
492500
}
@@ -564,6 +572,10 @@
564572
{
565573
"label": "Arrays",
566574
"to": "framework/angular/examples/array"
575+
},
576+
{
577+
"label": "Form Composition",
578+
"to": "framework/angular/examples/large-form"
567579
}
568580
]
569581
},

docs/framework/angular/guides/basic-concepts.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,21 +40,51 @@ Each field has its own state, which includes its current value, validation statu
4040

4141
Example:
4242

43-
```tsx
43+
```ts
4444
const {
4545
value,
4646
meta: { errors, isValidating },
4747
} = field.state
4848
```
4949

50-
There are three field states can be very useful to see how the user interacts with a field. A field is _"touched"_ when the user clicks/tabs into it, _"pristine"_ until the user changes value in it, and _"dirty"_ after the value has been changed. You can check these states via the `isTouched`, `isPristine` and `isDirty` flags, as seen below.
50+
There are four states in the metadata that can be useful to see how the user interacts with a field:
5151

52-
```tsx
53-
const { isTouched, isPristine, isDirty } = field.state.meta
52+
- _"isTouched"_, after the user changes the field or blurs the field
53+
- _"isDirty"_, after the field's value has been changed, even if it's been reverted to the default. Opposite of _"isPristine"_
54+
- _"isPristine"_, until the user changes the field value. Opposite of _"isDirty"_
55+
- _"isBlurred"_, after the field has been blurred
56+
57+
```ts
58+
const { isTouched, isDirty, isPristine, isBlurred } = field.state.meta
5459
```
5560

5661
![Field states](https://raw.githubusercontent.com/TanStack/form/main/docs/assets/field-states.png)
5762

63+
## Understanding 'isDirty' in Different Libraries
64+
65+
Non-Persistent `dirty` state
66+
67+
- **Libraries**: React Hook Form (RHF), Formik, Final Form.
68+
- **Behavior**: A field is 'dirty' if its value differs from the default. Reverting to the default value makes it 'clean' again.
69+
70+
Persistent `dirty` state
71+
72+
- **Libraries**: Angular Form, Vue FormKit.
73+
- **Behavior**: A field remains 'dirty' once changed, even if reverted to the default value.
74+
75+
We have chosen the persistent 'dirty' state model. To also support a non-persistent 'dirty' state, we introduce an additional flag:
76+
77+
- _"isDefaultValue"_, whether the field's current value is the default value
78+
79+
```ts
80+
const { isDefaultValue, isTouched } = field.state.meta
81+
82+
// The following line will re-create the non-Persistent `dirty` functionality.
83+
const nonPersistentIsDirty = !isDefaultValue
84+
```
85+
86+
![Field states extended](https://raw.githubusercontent.com/TanStack/form/main/docs/assets/field-states-extended.png)
87+
5888
## Field API
5989

6090
The Field API is an object accessed in the `tanstackField.api` property when creating a field. It provides methods for working with the field's state.
@@ -228,7 +258,7 @@ onCountryChange: FieldListenerFn<any, any, any, any, string> = ({
228258
}
229259
```
230260

231-
More information can be found at [Listeners](./listeners.md)
261+
More information can be found at [Listeners](../listeners.md)
232262

233263
## Array Fields
234264

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
---
2+
id: form-composition
3+
title: Form Composition
4+
---
5+
6+
A common criticism of TanStack Form is its verbosity out-of-the-box. While this _can_ be useful for educational purposes - helping enforce understanding our APIs - it's not ideal in production use cases.
7+
8+
As a result, while basic usage of `[tanstackField]` enables the most powerful and flexible usage of TanStack Form, we provide APIs that wrap it and make your application code less verbose.
9+
10+
## Pre-bound Field Components
11+
12+
If you've ever used TanStack Form in Angular to bind more than one input, you'll have quickly realized how much goes into each input:
13+
14+
```angular-ts
15+
import { Component } from '@angular/core'
16+
import { TanStackField, injectForm, injectStore } from '@tanstack/angular-form'
17+
18+
@Component({
19+
selector: 'app-root',
20+
standalone: true,
21+
imports: [TanStackField],
22+
template: `
23+
<div>
24+
<ng-container
25+
[tanstackField]="form"
26+
name="firstName"
27+
#firstName="field"
28+
>
29+
<label [for]="firstName.api.name">First Name:</label>
30+
<input
31+
[id]="firstName.api.name"
32+
[name]="firstName.api.name"
33+
[value]="firstName.api.state.value"
34+
(blur)="firstName.api.handleBlur()"
35+
(input)="firstName.api.handleChange($any($event).target.value)"
36+
/>
37+
@if (firstName.api.state.meta.isTouched) {
38+
@for (error of firstName.api.state.meta.errors; track $index) {
39+
<div style="color: red">
40+
{{ error }}
41+
</div>
42+
}
43+
}
44+
@if (firstName.api.state.meta.isValidating) {
45+
<p>Validating...</p>
46+
}
47+
</ng-container>
48+
</div>
49+
<div>
50+
<ng-container
51+
[tanstackField]="form"
52+
name="lastName"
53+
#lastName="field"
54+
>
55+
<label [for]="lastName.api.name">Last Name:</label>
56+
<input
57+
[id]="lastName.api.name"
58+
[name]="lastName.api.name"
59+
[value]="lastName.api.state.value"
60+
(blur)="lastName.api.handleBlur()"
61+
(input)="lastName.api.handleChange($any($event).target.value)"
62+
/>
63+
@if (lastName.api.state.meta.isTouched) {
64+
@for (error of lastName.api.state.meta.errors; track $index) {
65+
<div style="color: red">
66+
{{ error }}
67+
</div>
68+
}
69+
}
70+
@if (lastName.api.state.meta.isValidating) {
71+
<p>Validating...</p>
72+
}
73+
</ng-container>
74+
</div>
75+
`,
76+
})
77+
export class AppComponent {
78+
form = injectForm({
79+
defaultValues: {
80+
firstName: '',
81+
lastName: '',
82+
},
83+
onSubmit({ value }) {
84+
// Do something with form data
85+
console.log(value)
86+
},
87+
})
88+
}
89+
```
90+
91+
This is functionally correct, but introduces a _lot_ of repeated templating behavior over and over. Instead, let's move the error handling, label to input binding, and other repeated logic into a component:
92+
93+
```angular-ts
94+
import {injectField} from '@tanstack/angular-form'
95+
96+
@Component({
97+
selector: 'app-text-field',
98+
standalone: true,
99+
template: `
100+
<label [for]="field.api.name">{{ label() }}</label>
101+
<input
102+
[id]="field.api.name"
103+
[name]="field.api.name"
104+
[value]="field.api.state.value"
105+
(blur)="field.api.handleBlur()"
106+
(input)="field.api.handleChange($any($event).target.value)"
107+
/>
108+
@if (field.api.state.meta.isTouched) {
109+
@for (error of field.api.state.meta.errors; track $index) {
110+
<div style="color: red">
111+
{{ error }}
112+
</div>
113+
}
114+
}
115+
@if (field.api.state.meta.isValidating) {
116+
<p>Validating...</p>
117+
}
118+
`,
119+
})
120+
export class AppTextField {
121+
label = input.required<string>()
122+
// This API requires another part to it from the parent component
123+
field = injectField<string>()
124+
}
125+
```
126+
127+
> `injectField` accepts a single generic to define the `field.state.value` type.
128+
>
129+
> As a result, a numerical text field would be represented as `injectField<number>`, for example.
130+
131+
Now, we can use the `TanStackAppField` directive (`tanstack-app-field`) to `provide` the expected field associated with this input:
132+
133+
```angular-ts
134+
import { Component } from '@angular/core'
135+
import {
136+
TanStackAppField,
137+
TanStackField,
138+
injectForm,
139+
} from '@tanstack/angular-form'
140+
141+
@Component({
142+
selector: 'app-root',
143+
standalone: true,
144+
imports: [TanStackField, TanStackAppField, AppTextField],
145+
template: `
146+
<div>
147+
<app-text-field
148+
label="First name:"
149+
tanstack-app-field
150+
[tanstackField]="form"
151+
name="firstName"
152+
/>
153+
</div>
154+
<div>
155+
<app-text-field
156+
label="Last name:"
157+
tanstack-app-field
158+
[tanstackField]="form"
159+
name="lastName"
160+
/>
161+
</div>
162+
`,
163+
})
164+
export class AppComponent {
165+
form = injectForm({
166+
defaultValues: {
167+
firstName: '',
168+
lastName: '',
169+
},
170+
onSubmit({ value }) {
171+
// Do something with form data
172+
console.log(value)
173+
},
174+
})
175+
}
176+
```
177+
178+
> Here, the `tanstack-app-field` directive is taking the properties from `[tanstackField]` and `provide`ing them down to the `app-text-field` so that they can be more easily consumed as a component.

0 commit comments

Comments
 (0)