Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Array and provider refactor #588

Merged
merged 27 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ad6b2e1
feat: support array notation in DeepKeys and DeepValue types
crutchcorn Jan 28, 2024
41afa97
fix: support numerical values for arrays in DeepValue better
crutchcorn Jan 28, 2024
87c3eac
feat!: remove form.Provider from React package
crutchcorn Jan 28, 2024
ee69c9b
chore: fix utils tests in regards to arrays
crutchcorn Jan 28, 2024
5f56e9c
feat!: remove form.Provider from Solid package
crutchcorn Jan 28, 2024
20803b8
chore!: proof of working arrays in react
crutchcorn Jan 28, 2024
e7491f9
Merge branch 'main' into array-and-provider-refactor
crutchcorn Feb 29, 2024
acd30d9
Merge branch 'main' into array-and-provider-refactor
crutchcorn Mar 5, 2024
ce52a3a
chore: change Vue behavior and types
crutchcorn Mar 5, 2024
ce7c6bd
chore: fix CI, apply formatter, etc
crutchcorn Mar 5, 2024
2afe390
docs: remove mention of provider from docs
crutchcorn Mar 5, 2024
709167c
chore: add test to React array usage
crutchcorn Mar 5, 2024
9c2c73f
test: add test to Solid form
crutchcorn Mar 5, 2024
e3829ad
chore: fix Solid test
crutchcorn Mar 5, 2024
8302d49
chore: add array test to Vue
crutchcorn Mar 5, 2024
4468a46
chore: fix CI
crutchcorn Mar 5, 2024
ec09b38
docs: add array example to react docs
crutchcorn Mar 5, 2024
dfa5997
docs: improve the React basic concepts example
crutchcorn Mar 5, 2024
68c5375
chore: add React array example
crutchcorn Mar 5, 2024
cf623c0
docs: add SolidJS example and docs
crutchcorn Mar 5, 2024
be37e6b
chore: add Vue example
crutchcorn Mar 5, 2024
0f00a0e
docs: add array guide to Vue
crutchcorn Mar 5, 2024
6b3f58e
chore: fix CI
crutchcorn Mar 5, 2024
908d6f0
Merge branch 'main' into array-and-provider-refactor
crutchcorn Mar 9, 2024
c740617
chore: fix CI issues
crutchcorn Mar 9, 2024
1eb12b4
Apply suggestions from code review
crutchcorn Mar 13, 2024
1e1e48b
Merge branch 'main' into array-and-provider-refactor
crutchcorn Mar 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading