Skip to content

Commit

Permalink
feat: remove form.Provider and drastically improve array usage
Browse files Browse the repository at this point in the history
* feat: support array notation in DeepKeys and DeepValue types

* fix: support numerical values for arrays in DeepValue better

* feat!: remove form.Provider from React package

* chore: fix utils tests in regards to arrays

* feat!: remove form.Provider from Solid package

* chore!: proof of working arrays in react

* chore: change Vue behavior and types

* chore: fix CI, apply formatter, etc

* docs: remove mention of provider from docs

* chore: add test to React array usage

* test: add test to Solid form

* chore: fix Solid test

* chore: add array test to Vue

* chore: fix CI

* docs: add array example to react docs

* docs: improve the React basic concepts example

* chore: add React array example

* docs: add SolidJS example and docs

* chore: add Vue example

* docs: add array guide to Vue

* chore: fix CI

* chore: fix CI issues

* Apply suggestions from code review

Co-authored-by: fuko <[email protected]>

---------

Co-authored-by: fuko <[email protected]>
  • Loading branch information
crutchcorn and fulopkovacs authored Mar 13, 2024
1 parent 3d51d32 commit e833f1a
Show file tree
Hide file tree
Showing 87 changed files with 2,416 additions and 1,233 deletions.
34 changes: 34 additions & 0 deletions docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
{
"label": "react",
"children": [
{
"label": "Arrays",
"to": "framework/react/guides/arrays"
},
{
"label": "UI Libraries",
"to": "framework/react/guides/ui-libraries"
Expand All @@ -89,6 +93,24 @@
"to": "framework/react/guides/debugging"
}
]
},
{
"label": "vue",
"children": [
{
"label": "Arrays",
"to": "framework/vue/guides/arrays"
}
]
},
{
"label": "solid",
"children": [
{
"label": "Arrays",
"to": "framework/solid/guides/arrays"
}
]
}
]
},
Expand Down Expand Up @@ -185,6 +207,10 @@
"label": "Simple",
"to": "framework/react/examples/simple"
},
{
"label": "Arrays",
"to": "framework/react/examples/array"
},
{
"label": "Yup",
"to": "framework/react/examples/yup"
Expand All @@ -210,6 +236,10 @@
"label": "Simple",
"to": "framework/vue/examples/simple"
},
{
"label": "Arrays",
"to": "framework/vue/examples/array"
},
{
"label": "Yup",
"to": "framework/vue/examples/yup"
Expand All @@ -231,6 +261,10 @@
"label": "Simple",
"to": "framework/solid/examples/simple"
},
{
"label": "Arrays",
"to": "framework/solid/examples/array"
},
{
"label": "Yup",
"to": "framework/solid/examples/yup"
Expand Down
129 changes: 129 additions & 0 deletions docs/framework/react/guides/arrays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
id: arrays
title: Arrays
---

TanStack Form supports arrays as values in a form, including sub-object values inside of an array.

# Basic Usage

To use an array, you can use `field.state.value` on an array value:

```jsx
function App() {
const form = useForm({
defaultValues: {
people: [],
},
})

return (
<form.Field name="people" mode="array">
{(field) => (
<div>
{field.state.value.map((_, i) => {
// ...
})}
</div>
)}
</form.Field>
)
}
```

This will generate the mapped JSX every time you run `pushValue` on `field`:

```jsx
<button
onClick={() => field.pushValue({ name: '', age: 0 })}
type="button"
>
Add person
</button>
```

Finally, you can use a subfield like so:

```jsx
<form.Field key={i} name={`people[${i}].name`}>
{(subField) => (
<input
value={subField.state.value}
onChange={(e) =>
subField.handleChange(e.target.value)
}
/>
)}
</form.Field>
```

## Full Example

```jsx
function App() {
const form = useForm({
defaultValues: {
people: [],
},
onSubmit({ value }) {
alert(JSON.stringify(value))
}
})

return (
<div>
<form
onSubmit={(e) => {
e.preventDefault()
e.stopPropagation()
void form.handleSubmit()
}}
>
<form.Field name="people">
{(field) => {
return (
<div>
{field.state.value.map((_, i) => {
return (
<form.Field key={i} name={`people[${i}].name`}>
{(subField) => {
return (
<div>
<label>
<div>Name for person {i}</div>
<input
value={subField.state.value}
onChange={(e) =>
subField.handleChange(e.target.value)
}
/>
</label>
</div>
)
}}
</form.Field>
)
})}
<button
onClick={() => field.pushValue({ name: '', age: 0 })}
type="button"
>
Add person
</button>
</div>
)
}}
</form.Field>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
children={([canSubmit, isSubmitting]) => (
<button type="submit" disabled={!canSubmit}>
{isSubmitting ? '...' : 'Submit'}
</button>
)}
/>
</form>
</div>
)
}
```
7 changes: 2 additions & 5 deletions docs/framework/react/guides/ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const ClientComp = () => {
formFactory.initialFormState
);

const { useStore, Provider, Subscribe, handleSubmit, Field } =
const { useStore, Subscribe, handleSubmit, Field } =
formFactory.useForm({
transform: useTransform(
(baseForm: FormApi<any, any>) => mergeForm(baseForm, state),
Expand All @@ -120,7 +120,6 @@ const ClientComp = () => {

- Benefits of `formFactory`: Much like the useForm hook, formFactory streamlines the process of form management. It provides us with necessary functionalities such as:
- `useStore`: Observes and reflects the current state of the form on the client side.
- `Provider`: Acts as a context provider for the form, ensuring state and actions are accessible throughout the component.
- `Subscribe`: Enables the component to listen to form-specific events, like `canSubmit` and `isSubmitting`.
- `handleSubmit`: Orchestrates the submission logic of the form.
- `Field`: Manages individual form fields, adopting the `renderProps` pattern for greater flexibility.
Expand All @@ -136,7 +135,6 @@ const ClientComp = () => {
...

return (
<Provider>
<form action={action as never} onSubmit={() => handleSubmit()}>
{formErrors.map((error) => (
<p key={error as string}>{error}</p>
Expand Down Expand Up @@ -181,11 +179,10 @@ const ClientComp = () => {
)}
</Subscribe>
</form>
</Provider>
);
};
```

- In our UI, implementing the form is straightforward. We encapsulate our form within a `Provider`. A notable aspect here is the integration of our server action within the `form`. For the form's `action`, we utilize the `action` obtained from `useFormState`. This setup triggers the server action upon form submission. If everything processes successfully, the action will complete without issues. Otherwise, we'll encounter an error like "Server validation: You must be at least 12 to sign up."
- In our UI, implementing the form is straightforward. A notable aspect here is the integration of our server action within the `form`. For the form's `action`, we utilize the `action` obtained from `useFormState`. This setup triggers the server action upon form submission. If everything processes successfully, the action will complete without issues. Otherwise, we'll encounter an error like "Server validation: You must be at least 12 to sign up."

- You might now be wondering about client-side validation. How do we implement it? 🤔 The answer lies in the `handleSubmit` function. By assigning `handleSubmit` to the form's `onSubmit` event, we can handle client-side validation in the normal client side manner.
6 changes: 2 additions & 4 deletions docs/framework/react/guides/ui-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { TextInput, Checkbox } from '@mantine/core'
import { useForm } from '@tanstack/react-form'

export default function App() {
const { Provider, Field, handleSubmit, state } = useForm({
const { Field, handleSubmit, state } = useForm({
defaultValues: {
firstName: '',
lastName: '',
Expand All @@ -41,7 +41,6 @@ export default function App() {

return (
<>
<Provider>
<form
onSubmit={(e) => {
e.preventDefault()
Expand Down Expand Up @@ -70,7 +69,6 @@ export default function App() {
)}
/>
</form>
</Provider>
<div>
<pre>{JSON.stringify(state.values, null, 2)}</pre>
</div>
Expand All @@ -80,7 +78,7 @@ export default function App() {
```

- Initially, we utilize the `useForm` hook from TanStack and destructure the necessary properties. This step is optional; alternatively, you could use `const form = useForm()` if preferred. TypeScript's type inference ensures a smooth experience regardless of the approach.
- Next, we encapsulate our form elements within the `Provider` component, a critical step for enabling form functionalities. The `Field` component, derived from `useForm`, accepts several properties, such as `validators`. For this demonstration, we focus on two primary properties: `name` and `children`.
- The `Field` component, derived from `useForm`, accepts several properties, such as `validators`. For this demonstration, we focus on two primary properties: `name` and `children`.
- The `name` property identifies each `Field`, for instance, `firstName` in our example.
- The `children` property leverages the concept of render props, allowing us to integrate components without unnecessary abstractions.
- TanStack's design relies heavily on render props, providing access to `children` within the `Field` component. This approach is entirely type-safe. When integrating with Mantine components, such as `TextInput`, we selectively destructure properties like `state.value`, `handleChange`, and `handleBlur`. This selective approach is due to the slight differences in types between `TextInput` and the `field` we get in the children.
Expand Down
2 changes: 0 additions & 2 deletions docs/framework/react/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export default function App() {

return (
<div>
<form.Provider>
<form
onSubmit={(e) => {
e.preventDefault();
Expand All @@ -46,7 +45,6 @@ export default function App() {
</div>
<button type="submit">Submit</button>
</form>
</form.Provider>
</div>
)
}
Expand Down
4 changes: 0 additions & 4 deletions docs/framework/react/reference/formApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ title: Form API

When using `@tanstack/react-form`, the [core form API](../../reference/formApi) is extended at type level with additional methods for React-specific functionality:

- ```tsx
Provider: (props: PropsWithChildren) => JSX.Element
```
- React provider use to wrap your components. Reference React's [ContextProvider]("https://react.dev/reference/react/createContext#provider")
- ```tsx
Field: FieldComponent<TFormData, TFormValidator>
```
Expand Down
Loading

0 comments on commit e833f1a

Please sign in to comment.