Skip to content

Commit 7df4ac3

Browse files
committed
feat(devtools): impliment form devtools
1 parent 7dc843c commit 7df4ac3

55 files changed

Lines changed: 4335 additions & 619 deletions

Some content is hidden

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

codecov.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ coverage:
66
default:
77
target: auto
88
threshold: 1%
9+
ignore:
10+
- 'packages/form-devtools'
11+
- 'packages/react-form-devtools'
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
id: devtools
3+
title: Devtools
4+
---
5+
6+
TanStack Form comes with a ready to go suit of devtools.
7+
8+
## Setup
9+
10+
Install the [TanStack Devtools](https://tanstack.com/devtools/latest/docs/quick-start) library and the [TanStack Form plugin](http://npmjs.com/package/@tanstack/react-form-devtools), from the framework adapter that your working in (in this case `@tanstack/react-devtools`, and `@tanstack/react-form-devtools`).
11+
12+
```bash
13+
npm i @tanstack/react-devtools
14+
npm i @tanstack/react-form-devtools
15+
```
16+
17+
Next in the root of your application import the `TanstackDevtools`.
18+
19+
```tsx
20+
import { TanstackDevtools } from '@tanstack/react-devtools'
21+
22+
import App from './App'
23+
24+
createRoot(document.getElementById('root')!).render(
25+
<StrictMode>
26+
<App />
27+
28+
<TanstackDevtools />
29+
</StrictMode>,
30+
)
31+
```
32+
33+
Import the `FormDevtoolsPlugin` from **TanStack Form** and provide it to the `TanstackDevtools` component.
34+
35+
```tsx
36+
import { TanstackDevtools } from '@tanstack/react-devtools'
37+
import { FormDevtoolsPlugin } from '@tanstack/react-form-devtools'
38+
39+
import App from './App'
40+
41+
createRoot(document.getElementById('root')!).render(
42+
<StrictMode>
43+
<App />
44+
45+
<TanstackDevtools plugins={[FormDevtoolsPlugin()]} />
46+
</StrictMode>,
47+
)
48+
```
49+
50+
Finally add any additional configuration you desire to the `TanstackDevtools` component, more information can be found under the [TanStack Devtools Configuration](https://tanstack.com/devtools/) section.
51+
52+
A complete working example can be found in our [examples section](https://tanstack.com/form/latest/docs/framework/react/examples/devtools).
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// @ts-check
2+
3+
/** @type {import('eslint').Linter.Config} */
4+
const config = {
5+
extends: ['plugin:react/recommended', 'plugin:react-hooks/recommended'],
6+
rules: {
7+
'react/no-children-prop': 'off',
8+
},
9+
}
10+
11+
module.exports = config

examples/react/devtools/.gitignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# production
12+
/build
13+
14+
pnpm-lock.yaml
15+
yarn.lock
16+
package-lock.json
17+
18+
# misc
19+
.DS_Store
20+
.env.local
21+
.env.development.local
22+
.env.test.local
23+
.env.production.local
24+
25+
npm-debug.log*
26+
yarn-debug.log*
27+
yarn-error.log*

examples/react/devtools/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Example
2+
3+
To run this example:
4+
5+
- `npm install`
6+
- `npm run dev`

examples/react/devtools/index.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<link rel="icon" type="image/svg+xml" href="/emblem-light.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta name="theme-color" content="#000000" />
8+
9+
<title>TanStack Form Devtools Example App</title>
10+
</head>
11+
<body>
12+
<noscript>You need to enable JavaScript to run this app.</noscript>
13+
<div id="root"></div>
14+
<script type="module" src="/src/index.tsx"></script>
15+
</body>
16+
</html>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "@tanstack/form-example-react-devtools",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite --port=3001",
7+
"build": "vite build",
8+
"preview": "vite preview",
9+
"test:types": "tsc"
10+
},
11+
"dependencies": {
12+
"@tanstack/react-devtools": "^0.6.4",
13+
"@tanstack/react-form": "workspace:*",
14+
"@tanstack/react-form-devtools": "workspace:*",
15+
"react": "^19.0.0",
16+
"react-dom": "^19.0.0"
17+
},
18+
"devDependencies": {
19+
"@types/react": "^19.0.7",
20+
"@types/react-dom": "^19.0.3",
21+
"@vitejs/plugin-react": "^4.7.0",
22+
"vite": "^7.1.5"
23+
},
24+
"browserslist": {
25+
"production": [
26+
">0.2%",
27+
"not dead",
28+
"not op_mini all"
29+
],
30+
"development": [
31+
"last 1 chrome version",
32+
"last 1 firefox version",
33+
"last 1 safari version"
34+
]
35+
}
36+
}
Lines changed: 13 additions & 0 deletions
Loading
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { useForm } from '@tanstack/react-form'
2+
3+
import type { AnyFieldApi } from '@tanstack/react-form'
4+
5+
function FieldInfo({ field }: { field: AnyFieldApi }) {
6+
return (
7+
<>
8+
{field.state.meta.isTouched && !field.state.meta.isValid ? (
9+
<em>{field.state.meta.errors.join(',')}</em>
10+
) : null}
11+
{field.state.meta.isValidating ? 'Validating...' : null}
12+
</>
13+
)
14+
}
15+
16+
export default function App() {
17+
const form1 = useForm({
18+
defaultValues: {
19+
firstName: '',
20+
lastName: '',
21+
},
22+
onSubmit: async ({ value }) => {
23+
console.log(value)
24+
},
25+
})
26+
27+
const form2 = useForm({
28+
defaultValues: {
29+
age: 0,
30+
},
31+
validators: {
32+
onChange({ value }) {
33+
if (value.age < 15) {
34+
return 'needs to be above 15'
35+
}
36+
return undefined
37+
},
38+
},
39+
onSubmit: async ({ value }) => {
40+
if (value.age < 20) throw 'needs to be above 20'
41+
},
42+
})
43+
44+
return (
45+
<div>
46+
<h1>Form Devtools Example</h1>
47+
48+
<h2>Form 1</h2>
49+
50+
<form
51+
onSubmit={(e) => {
52+
e.preventDefault()
53+
e.stopPropagation()
54+
form1.handleSubmit()
55+
}}
56+
>
57+
<div>
58+
<form1.Field
59+
name="firstName"
60+
validators={{
61+
onChange: ({ value }) =>
62+
!value
63+
? 'A first name is required'
64+
: value.length < 3
65+
? 'First name must be at least 3 characters'
66+
: undefined,
67+
}}
68+
children={(field) => {
69+
return (
70+
<>
71+
<label htmlFor={field.name}>First Name:</label>
72+
<input
73+
id={field.name}
74+
name={field.name}
75+
value={field.state.value}
76+
onBlur={field.handleBlur}
77+
onChange={(e) => field.handleChange(e.target.value)}
78+
/>
79+
<FieldInfo field={field} />
80+
</>
81+
)
82+
}}
83+
/>
84+
</div>
85+
86+
<div>
87+
<form1.Field
88+
name="lastName"
89+
children={(field) => (
90+
<>
91+
<label htmlFor={field.name}>Last Name:</label>
92+
<input
93+
id={field.name}
94+
name={field.name}
95+
value={field.state.value}
96+
onBlur={field.handleBlur}
97+
onChange={(e) => field.handleChange(e.target.value)}
98+
/>
99+
<FieldInfo field={field} />
100+
</>
101+
)}
102+
/>
103+
</div>
104+
105+
<form1.Subscribe
106+
selector={(state) => [state.canSubmit, state.isSubmitting]}
107+
children={([canSubmit, isSubmitting]) => (
108+
<>
109+
<button type="submit" disabled={!canSubmit}>
110+
{isSubmitting ? '...' : 'Submit'}
111+
</button>
112+
<button
113+
type="reset"
114+
onClick={(e) => {
115+
e.preventDefault()
116+
form1.reset()
117+
}}
118+
>
119+
Reset
120+
</button>
121+
</>
122+
)}
123+
/>
124+
125+
<form1.Subscribe
126+
selector={(state) => [state.submissionAttempts]}
127+
children={([submissionAttempts]) => (
128+
<div>submission attempts: {submissionAttempts}</div>
129+
)}
130+
/>
131+
</form>
132+
133+
<h2>Form 2</h2>
134+
<form
135+
onSubmit={(e) => {
136+
e.preventDefault()
137+
e.stopPropagation()
138+
form2.handleSubmit()
139+
}}
140+
>
141+
<div>
142+
<form2.Field
143+
name="age"
144+
validators={{
145+
onChange: ({ value }) =>
146+
value < 10 ? 'Age must be at least 10' : undefined,
147+
}}
148+
children={(field) => {
149+
return (
150+
<>
151+
<label htmlFor={field.name}>age:</label>
152+
<input
153+
type="number"
154+
id={field.name}
155+
name={field.name}
156+
value={field.state.value}
157+
onBlur={field.handleBlur}
158+
onChange={(e) =>
159+
field.handleChange(parseInt(e.target.value))
160+
}
161+
/>
162+
<FieldInfo field={field} />
163+
</>
164+
)
165+
}}
166+
/>
167+
168+
<form2.Subscribe
169+
selector={(state) => [state.submissionAttempts]}
170+
children={([submissionAttempts]) => (
171+
<div>submission attempts: {submissionAttempts}</div>
172+
)}
173+
/>
174+
</div>
175+
</form>
176+
</div>
177+
)
178+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { StrictMode } from 'react'
2+
import { createRoot } from 'react-dom/client'
3+
4+
import { TanStackDevtools } from '@tanstack/react-devtools'
5+
import { FormDevtoolsPlugin } from '@tanstack/react-form-devtools'
6+
7+
import App from './App'
8+
9+
createRoot(document.getElementById('root')!).render(
10+
<StrictMode>
11+
<App />
12+
13+
<TanStackDevtools
14+
config={{ hideUntilHover: true }}
15+
plugins={[FormDevtoolsPlugin()]}
16+
/>
17+
</StrictMode>,
18+
)

0 commit comments

Comments
 (0)