feat: solid.js form (#471)

* solid commit -a

* fix failing tests and formatting

* comments + removed unneeded computed

* updated changes

* prettierd

* chore: add Solid Form to script to be deployed

* fix: fix typing of solid's Subscribe data

* chore: remove errant createEffect

* chore: rename Solid's useForm and useField to createForm and createField

* chore: remove old mention of React's memoization

* chore: add Solid simple example

* chore: add Solid yup example

* chore: add Zod Solid example

* docs: add initial docs for Solid package

---------

Co-authored-by: Corbin Crutchley <git@crutchcorn.dev>
This commit is contained in:
Aadit Olkar
2023-10-30 15:17:49 +08:00
committed by GitHub
parent 1b394a7e72
commit 6050fea779
67 changed files with 5179 additions and 1882 deletions

View File

@@ -16,7 +16,6 @@ function FieldInfo({ field }: { field: FieldApi<any, any, unknown, unknown> }) {
export default function App() {
const form = useForm({
// Memoize your default values to prevent re-renders
defaultValues: {
firstName: "",
lastName: "",
@@ -30,7 +29,6 @@ export default function App() {
return (
<div>
<h1>Simple Form Example</h1>
{/* A pre-bound form component */}
<form.Provider>
<form
onSubmit={(e) => {
@@ -40,7 +38,7 @@ export default function App() {
}}
>
<div>
{/* A type-safe and pre-bound field component*/}
{/* A type-safe field component*/}
<form.Field
name="firstName"
onChange={(value) =>

View File

@@ -18,7 +18,6 @@ function FieldInfo({ field }: { field: FieldApi<any, any, unknown, unknown> }) {
export default function App() {
const form = useForm({
// Memoize your default values to prevent re-renders
defaultValues: {
firstName: "",
lastName: "",
@@ -33,8 +32,7 @@ export default function App() {
return (
<div>
<h1>Simple Form Example</h1>
{/* A pre-bound form component */}
<h1>Yup Form Example</h1>
<form.Provider>
<form
onSubmit={(e) => {
@@ -44,7 +42,7 @@ export default function App() {
}}
>
<div>
{/* A type-safe and pre-bound field component*/}
{/* A type-safe field component*/}
<form.Field
name="firstName"
onChange={yup

View File

@@ -18,7 +18,6 @@ function FieldInfo({ field }: { field: FieldApi<any, any, unknown, unknown> }) {
export default function App() {
const form = useForm({
// Memoize your default values to prevent re-renders
defaultValues: {
firstName: "",
lastName: "",
@@ -33,8 +32,7 @@ export default function App() {
return (
<div>
<h1>Simple Form Example</h1>
{/* A pre-bound form component */}
<h1>Zod Form Example</h1>
<form.Provider>
<form
onSubmit={(e) => {
@@ -44,7 +42,7 @@ export default function App() {
}}
>
<div>
{/* A type-safe and pre-bound field component*/}
{/* A type-safe field component*/}
<form.Field
name="firstName"
onChange={z

24
examples/solid/simple/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,28 @@
## Usage
```bash
$ npm install # or pnpm install or yarn install
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Available Scripts
In the project directory, you can run:
### `npm run dev`
Runs the app in the development mode.<br>
Open [http://localhost:5173](http://localhost:5173) to view it in the browser.
### `npm run build`
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## Deployment
Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html)

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TanStack Form Solid Simple Example App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,37 @@
{
"name": "@tanstack/form-example-solid-simple",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test:types": "tsc --noEmit",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/form-core": "0.4.1",
"@tanstack/solid-form": "0.4.1",
"solid-js": "^1.7.8",
"@tanstack/react-form": "0.4.1",
"@tanstack/zod-form-adapter": "0.4.1",
"@tanstack/yup-form-adapter": "0.4.1"
},
"devDependencies": {
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-plugin-solid": "^2.7.0"
},
"nx": {
"implicitDependencies": [
"@tanstack/form-core",
"@tanstack/solid-form"
],
"targets": {
"test:types": {
"dependsOn": [
"build"
]
}
}
}
}

View File

@@ -0,0 +1,117 @@
/* @refresh reload */
import { render } from 'solid-js/web'
import { createForm, type FieldApi } from '@tanstack/solid-form'
interface FieldInfoProps {
field: FieldApi<any, any, unknown, unknown>
}
function FieldInfo(props: FieldInfoProps) {
return (
<>
{props.field.state.meta.touchedErrors ? (
<em>{props.field.state.meta.touchedErrors}</em>
) : null}
{props.field.state.meta.isValidating ? 'Validating...' : null}
</>
)
}
function App() {
const form = createForm(() => ({
defaultValues: {
firstName: '',
lastName: '',
},
onSubmit: async (values) => {
// Do something with form data
console.log(values)
},
}))
return (
<div>
<h1>Simple Form Example</h1>
<form.Provider>
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
void form.handleSubmit()
}}
>
<div>
{/* A type-safe field component*/}
<form.Field
name="firstName"
onChange={(value) =>
!value
? 'A first name is required'
: value.length < 3
? 'First name must be at least 3 characters'
: undefined
}
onChangeAsyncDebounceMs={500}
onChangeAsync={async (value) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return (
value.includes('error') && 'No "error" allowed in first name'
)
}}
children={(field) => {
// Avoid hasty abstractions. Render props are great!
return (
<>
<label for={field().name}>First Name:</label>
<input
name={field().name}
value={field().state.value}
onBlur={field().handleBlur}
onInput={(e) => field().handleChange(e.target.value)}
/>
<FieldInfo field={field()} />
</>
)
}}
/>
</div>
<div>
<form.Field
name="lastName"
children={(field) => (
<>
<label for={field().name}>Last Name:</label>
<input
name={field().name}
value={field().state.value}
onBlur={field().handleBlur}
onInput={(e) => field().handleChange(e.target.value)}
/>
<FieldInfo field={field()} />
</>
)}
/>
</div>
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
children={(state) => {
return (
<button type="submit" disabled={!state().canSubmit}>
{state().isSubmitting ? '...' : 'Submit'}
</button>
)
}}
/>
</form>
</form.Provider>
</div>
)
}
const root = document.getElementById('root')
render(() => <App />, root!)

View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,6 @@
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
export default defineConfig({
plugins: [solid()],
})

24
examples/solid/yup/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,28 @@
## Usage
```bash
$ npm install # or pnpm install or yarn install
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Available Scripts
In the project directory, you can run:
### `npm run dev`
Runs the app in the development mode.<br>
Open [http://localhost:5173](http://localhost:5173) to view it in the browser.
### `npm run build`
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## Deployment
Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html)

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TanStack Form Solid Yup Example App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,39 @@
{
"name": "@tanstack/form-example-solid-yup",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test:types": "tsc --noEmit",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/form-core": "0.4.1",
"@tanstack/solid-form": "0.4.1",
"@tanstack/yup-form-adapter": "0.4.1",
"solid-js": "^1.7.8",
"yup": "^1.3.2",
"@tanstack/react-form": "0.4.1",
"@tanstack/zod-form-adapter": "0.4.1"
},
"devDependencies": {
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-plugin-solid": "^2.7.0"
},
"nx": {
"implicitDependencies": [
"@tanstack/form-core",
"@tanstack/solid-form",
"@tanstack/yup-form-adapter"
],
"targets": {
"test:types": {
"dependsOn": [
"build"
]
}
}
}
}

View File

@@ -0,0 +1,121 @@
/* @refresh reload */
import { render } from 'solid-js/web'
import { createForm, type FieldApi } from '@tanstack/solid-form'
import { yupValidator } from '@tanstack/yup-form-adapter'
import * as yup from 'yup'
interface FieldInfoProps {
field: FieldApi<any, any, unknown, unknown>
}
function FieldInfo(props: FieldInfoProps) {
return (
<>
{props.field.state.meta.touchedErrors ? (
<em>{props.field.state.meta.touchedErrors}</em>
) : null}
{props.field.state.meta.isValidating ? 'Validating...' : null}
</>
)
}
function App() {
const form = createForm(() => ({
defaultValues: {
firstName: '',
lastName: '',
},
onSubmit: async (values) => {
// Do something with form data
console.log(values)
},
// Add a validator to support Yup usage in Form and Field
validator: yupValidator,
}))
return (
<div>
<h1>Yup Form Example</h1>
<form.Provider>
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
void form.handleSubmit()
}}
>
<div>
{/* A type-safe field component*/}
<form.Field
name="firstName"
onChange={yup
.string()
.min(3, 'First name must be at least 3 characters')}
onChangeAsyncDebounceMs={500}
onChangeAsync={yup
.string()
.test(
'no error',
"No 'error' allowed in first name",
async (value) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return !value?.includes('error')
},
)}
children={(field) => {
// Avoid hasty abstractions. Render props are great!
return (
<>
<label for={field().name}>First Name:</label>
<input
name={field().name}
value={field().state.value}
onBlur={field().handleBlur}
onInput={(e) => field().handleChange(e.target.value)}
/>
<FieldInfo field={field()} />
</>
)
}}
/>
</div>
<div>
<form.Field
name="lastName"
children={(field) => (
<>
<label for={field().name}>Last Name:</label>
<input
name={field().name}
value={field().state.value}
onBlur={field().handleBlur}
onInput={(e) => field().handleChange(e.target.value)}
/>
<FieldInfo field={field()} />
</>
)}
/>
</div>
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
children={(state) => {
return (
<button type="submit" disabled={!state().canSubmit}>
{state().isSubmitting ? '...' : 'Submit'}
</button>
)
}}
/>
</form>
</form.Provider>
</div>
)
}
const root = document.getElementById('root')
render(() => <App />, root!)

1
examples/solid/yup/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,6 @@
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
export default defineConfig({
plugins: [solid()],
})

24
examples/solid/zod/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,28 @@
## Usage
```bash
$ npm install # or pnpm install or yarn install
```
### Learn more on the [Solid Website](https://solidjs.com) and come chat with us on our [Discord](https://discord.com/invite/solidjs)
## Available Scripts
In the project directory, you can run:
### `npm run dev`
Runs the app in the development mode.<br>
Open [http://localhost:5173](http://localhost:5173) to view it in the browser.
### `npm run build`
Builds the app for production to the `dist` folder.<br>
It correctly bundles Solid in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br>
Your app is ready to be deployed!
## Deployment
Learn more about deploying your application with the [documentations](https://vitejs.dev/guide/static-deploy.html)

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TanStack Form Solid Zod Example App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,39 @@
{
"name": "@tanstack/form-example-solid-zod",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"test:types": "tsc --noEmit",
"preview": "vite preview"
},
"dependencies": {
"@tanstack/form-core": "0.4.1",
"@tanstack/solid-form": "0.4.1",
"@tanstack/zod-form-adapter": "0.4.1",
"solid-js": "^1.7.8",
"zod": "^3.21.4",
"@tanstack/react-form": "0.4.1",
"@tanstack/yup-form-adapter": "0.4.1"
},
"devDependencies": {
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-plugin-solid": "^2.7.0"
},
"nx": {
"implicitDependencies": [
"@tanstack/form-core",
"@tanstack/solid-form",
"@tanstack/zod-form-adapter"
],
"targets": {
"test:types": {
"dependsOn": [
"build"
]
}
}
}
}

View File

@@ -0,0 +1,120 @@
/* @refresh reload */
import { render } from 'solid-js/web'
import { createForm, type FieldApi } from '@tanstack/solid-form'
import { zodValidator } from '@tanstack/zod-form-adapter'
import { z } from 'zod'
interface FieldInfoProps {
field: FieldApi<any, any, unknown, unknown>
}
function FieldInfo(props: FieldInfoProps) {
return (
<>
{props.field.state.meta.touchedErrors ? (
<em>{props.field.state.meta.touchedErrors}</em>
) : null}
{props.field.state.meta.isValidating ? 'Validating...' : null}
</>
)
}
function App() {
const form = createForm(() => ({
defaultValues: {
firstName: '',
lastName: '',
},
onSubmit: async (values) => {
// Do something with form data
console.log(values)
},
// Add a validator to support Zod usage in Form and Field
validator: zodValidator,
}))
return (
<div>
<h1>Zod Form Example</h1>
<form.Provider>
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
void form.handleSubmit()
}}
>
<div>
{/* A type-safe field component*/}
<form.Field
name="firstName"
onChange={z
.string()
.min(3, 'First name must be at least 3 characters')}
onChangeAsyncDebounceMs={500}
onChangeAsync={z.string().refine(
async (value) => {
await new Promise((resolve) => setTimeout(resolve, 1000))
return !value.includes('error')
},
{
message: "No 'error' allowed in first name",
},
)}
children={(field) => {
// Avoid hasty abstractions. Render props are great!
return (
<>
<label for={field().name}>First Name:</label>
<input
name={field().name}
value={field().state.value}
onBlur={field().handleBlur}
onInput={(e) => field().handleChange(e.target.value)}
/>
<FieldInfo field={field()} />
</>
)
}}
/>
</div>
<div>
<form.Field
name="lastName"
children={(field) => (
<>
<label for={field().name}>Last Name:</label>
<input
name={field().name}
value={field().state.value}
onBlur={field().handleBlur}
onInput={(e) => field().handleChange(e.target.value)}
/>
<FieldInfo field={field()} />
</>
)}
/>
</div>
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
children={(state) => {
return (
<button type="submit" disabled={!state().canSubmit}>
{state().isSubmitting ? '...' : 'Submit'}
</button>
)
}}
/>
</form>
</form.Provider>
</div>
)
}
const root = document.getElementById('root')
render(() => <App />, root!)

1
examples/solid/zod/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"jsxImportSource": "solid-js",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,6 @@
import { defineConfig } from 'vite'
import solid from 'vite-plugin-solid'
export default defineConfig({
plugins: [solid()],
})