From 7a557cdd7a934f43b2d969d91deee8ac7e74c28d Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Fri, 20 Dec 2024 12:31:54 +0100 Subject: [PATCH 1/6] feat(core): docs for reusable fields & reusable field utility type --- .../framework/react/guides/reusable-fields.md | 94 +++++++++++++++++++ packages/form-core/src/util-types.ts | 12 +++ 2 files changed, 106 insertions(+) create mode 100644 docs/framework/react/guides/reusable-fields.md diff --git a/docs/framework/react/guides/reusable-fields.md b/docs/framework/react/guides/reusable-fields.md new file mode 100644 index 000000000..93033218b --- /dev/null +++ b/docs/framework/react/guides/reusable-fields.md @@ -0,0 +1,94 @@ +--- +id: reusable-fields +title: Creating reusable fields +--- + +As TanStack form is a headless library, we provide you the core building blocks and then give you the freedom to build on top of them. This page introduces the concept of creating reusable field-components for your form, allowing you to create fields that you can reuse throughout your app. + +## Basic Usage + +To create a reusable fields, you can do the following. + +```tsx +import { useForm, Validator, InferValidFormKeys } from '@tanstack/react-form'; + +export default function GenericTextField< + TForm, + TName extends InferValidFormKeys, + TFormValidator extends Validator | undefined, +>({ name, form }: { + name: TName; + form: ReturnType +> }): JSX.Element { + return ( + + {({ handleChange, handleBlur, state }) => ( + handleChange(e.target.value as any)} + onBlur={handleBlur} + /> + )} + + ); +} +``` + +In this setup the GenericTextField will only accept names of fields that have a valid value type, in this case a string, as shown here. + +```tsx + TName extends InferValidFormKeys, +``` + +Deep values can also be inferred using this method from the parent form. + +```tsx +function App() { + const form = useForm({ + defaultValues: { name: '', id: 0, interests: {hobbies: 'climbing'} }, + onSubmit: ({ value }) => { + console.log(value); + }, + }); + + return ; +} +``` + +## Full Example + +```tsx +import { useForm, Validator, InferValidFormKeys } from '@tanstack/react-form'; + +export default function GenericTextField< + TForm, + TName extends InferValidFormKeys, + TFormValidator extends Validator | undefined, +>({ name, form }: { + name: TName; + form: ReturnType +> }): JSX.Element { + return ( + + {({ handleChange, handleBlur, state }) => ( + handleChange(e.target.value as any)} + onBlur={handleBlur} + /> + )} + + ); +} + +function App() { + const form = useForm({ + defaultValues: { name: '', id: 0 }, + onSubmit: ({ value }) => { + console.log(value); + }, + }); + + return ; +} +``` diff --git a/packages/form-core/src/util-types.ts b/packages/form-core/src/util-types.ts index 8a9a107cd..d5e2723ea 100644 --- a/packages/form-core/src/util-types.ts +++ b/packages/form-core/src/util-types.ts @@ -144,3 +144,15 @@ export type DeepValue< : never : // Do not allow `TValue` to be anything else never + +/** + * Infers the form keys of valid fields + */ +export type InferValidFormKeys< + // Form generic slot + TForm, + // Desired type of the generic component + TFieldType, +> = { + [K in DeepKeys]: DeepValue extends TFieldType ? K : never +}[DeepKeys] From 5a0ea71b89c7283c017bf3c4a072bfd6fd13efe4 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 13:00:20 +0000 Subject: [PATCH 2/6] ci: apply automated fixes and generate docs --- docs/reference/index.md | 1 + .../type-aliases/infervalidformkeys.md | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 docs/reference/type-aliases/infervalidformkeys.md diff --git a/docs/reference/index.md b/docs/reference/index.md index 6221bdca0..44418942c 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -34,6 +34,7 @@ title: "@tanstack/form-core" - [FormState](type-aliases/formstate.md) - [FormValidateFn](type-aliases/formvalidatefn.md) - [FormValidator](type-aliases/formvalidator.md) +- [InferValidFormKeys](type-aliases/infervalidformkeys.md) - [StandardSchemaV1](type-aliases/standardschemav1.md) - [Updater](type-aliases/updater.md) - [UpdaterFn](type-aliases/updaterfn.md) diff --git a/docs/reference/type-aliases/infervalidformkeys.md b/docs/reference/type-aliases/infervalidformkeys.md new file mode 100644 index 000000000..de19b2242 --- /dev/null +++ b/docs/reference/type-aliases/infervalidformkeys.md @@ -0,0 +1,22 @@ +--- +id: InferValidFormKeys +title: InferValidFormKeys +--- + +# Type Alias: InferValidFormKeys\ + +```ts +type InferValidFormKeys: { [K in DeepKeys]: DeepValue extends TFieldType ? K : never }[DeepKeys]; +``` + +Infers the form keys of valid fields + +## Type Parameters + +• **TForm** + +• **TFieldType** + +## Defined in + +[packages/form-core/src/util-types.ts:151](https://github.com/TanStack/form/blob/main/packages/form-core/src/util-types.ts#L151) From 39c55a37c1d5021bd1f764eb5f053d24bf0b7600 Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Fri, 20 Dec 2024 13:13:01 +0100 Subject: [PATCH 3/6] fix(core): match default values between snippets and full example --- docs/framework/react/guides/reusable-fields.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/framework/react/guides/reusable-fields.md b/docs/framework/react/guides/reusable-fields.md index 93033218b..6541f37a3 100644 --- a/docs/framework/react/guides/reusable-fields.md +++ b/docs/framework/react/guides/reusable-fields.md @@ -17,8 +17,8 @@ export default function GenericTextField< TName extends InferValidFormKeys, TFormValidator extends Validator | undefined, >({ name, form }: { - name: TName; - form: ReturnType + name: TName; + form: ReturnType > }): JSX.Element { return ( @@ -65,8 +65,8 @@ export default function GenericTextField< TName extends InferValidFormKeys, TFormValidator extends Validator | undefined, >({ name, form }: { - name: TName; - form: ReturnType + name: TName; + form: ReturnType > }): JSX.Element { return ( @@ -83,7 +83,7 @@ export default function GenericTextField< function App() { const form = useForm({ - defaultValues: { name: '', id: 0 }, + defaultValues: { name: '', id: 0, interests: {hobbies: 'climbing'} }, onSubmit: ({ value }) => { console.log(value); }, From bb6c6a5fe416ef915708163abfdf8c51be538905 Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Fri, 20 Dec 2024 14:06:27 +0100 Subject: [PATCH 4/6] feat(react): UseFormReturnType utility type --- docs/framework/react/guides/reusable-fields.md | 16 ++++++++-------- packages/react-form/src/types.ts | 10 ++++++++++ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/docs/framework/react/guides/reusable-fields.md b/docs/framework/react/guides/reusable-fields.md index 6541f37a3..9a41a8f12 100644 --- a/docs/framework/react/guides/reusable-fields.md +++ b/docs/framework/react/guides/reusable-fields.md @@ -10,15 +10,15 @@ As TanStack form is a headless library, we provide you the core building blocks To create a reusable fields, you can do the following. ```tsx -import { useForm, Validator, InferValidFormKeys } from '@tanstack/react-form'; +import { InferValidFormKeys } from '@tanstack/react-form'; export default function GenericTextField< TForm, + TFormValidator TName extends InferValidFormKeys, - TFormValidator extends Validator | undefined, >({ name, form }: { name: TName; - form: ReturnType + form: UseFormReturnType, > }): JSX.Element { return ( @@ -45,7 +45,7 @@ Deep values can also be inferred using this method from the parent form. ```tsx function App() { const form = useForm({ - defaultValues: { name: '', id: 0, interests: {hobbies: 'climbing'} }, + defaultValues: { name: '', id: 0, interests: {hobbies: ''} }, onSubmit: ({ value }) => { console.log(value); }, @@ -58,15 +58,15 @@ function App() { ## Full Example ```tsx -import { useForm, Validator, InferValidFormKeys } from '@tanstack/react-form'; +import { InferValidFormKeys } from '@tanstack/react-form'; export default function GenericTextField< TForm, + TFormValidator TName extends InferValidFormKeys, - TFormValidator extends Validator | undefined, >({ name, form }: { name: TName; - form: ReturnType + form: UseFormReturnType > }): JSX.Element { return ( @@ -83,7 +83,7 @@ export default function GenericTextField< function App() { const form = useForm({ - defaultValues: { name: '', id: 0, interests: {hobbies: 'climbing'} }, + defaultValues: { name: '', id: 0, interests: {hobbies: ''} }, onSubmit: ({ value }) => { console.log(value); }, diff --git a/packages/react-form/src/types.ts b/packages/react-form/src/types.ts index 8f39d95cb..790378120 100644 --- a/packages/react-form/src/types.ts +++ b/packages/react-form/src/types.ts @@ -5,6 +5,7 @@ import type { Validator, } from '@tanstack/form-core' import type { FunctionComponent } from 'react' +import { useForm } from './useForm' /** * The field options. @@ -28,3 +29,12 @@ export type UseFieldOptions< > & { mode?: 'value' | 'array' } + +/** + * The return type use useForm with pre-populated generics + */ +export type UseFormReturnType = TFormValidator extends + | Validator + | undefined + ? ReturnType> + : never From 8a4523a3a33d0f69419558f71a1f450ca87a09ea Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Sun, 22 Dec 2024 17:15:16 +0100 Subject: [PATCH 5/6] fix(core): cleaned up generic slots --- docs/framework/react/guides/reusable-fields.md | 14 ++++++-------- packages/react-form/src/types.ts | 5 ++--- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/framework/react/guides/reusable-fields.md b/docs/framework/react/guides/reusable-fields.md index 9a41a8f12..ec1b12005 100644 --- a/docs/framework/react/guides/reusable-fields.md +++ b/docs/framework/react/guides/reusable-fields.md @@ -10,14 +10,13 @@ As TanStack form is a headless library, we provide you the core building blocks To create a reusable fields, you can do the following. ```tsx -import { InferValidFormKeys } from '@tanstack/react-form'; +import { InferValidFormKeys, UseFormReturnType } from '@tanstack/react-form'; export default function GenericTextField< TForm, TFormValidator - TName extends InferValidFormKeys, >({ name, form }: { - name: TName; + name: InferValidFormKeys; form: UseFormReturnType, > }): JSX.Element { return ( @@ -34,10 +33,10 @@ export default function GenericTextField< } ``` -In this setup the GenericTextField will only accept names of fields that have a valid value type, in this case a string, as shown here. +With the InferValidFormKeys the GenericTextField component's name prop will only accept the names of fields that have a valid value type, in this case a string, as shown here. ```tsx - TName extends InferValidFormKeys, + name: InferValidFormKeys; ``` Deep values can also be inferred using this method from the parent form. @@ -58,14 +57,13 @@ function App() { ## Full Example ```tsx -import { InferValidFormKeys } from '@tanstack/react-form'; +import { InferValidFormKeys, UseFormReturnType } from '@tanstack/react-form'; export default function GenericTextField< TForm, TFormValidator - TName extends InferValidFormKeys, >({ name, form }: { - name: TName; + name: InferValidFormKeys; form: UseFormReturnType > }): JSX.Element { return ( diff --git a/packages/react-form/src/types.ts b/packages/react-form/src/types.ts index 790378120..9e96058d5 100644 --- a/packages/react-form/src/types.ts +++ b/packages/react-form/src/types.ts @@ -4,8 +4,7 @@ import type { FieldApiOptions, Validator, } from '@tanstack/form-core' -import type { FunctionComponent } from 'react' -import { useForm } from './useForm' +import { ReactFormExtendedApi } from './useForm' /** * The field options. @@ -36,5 +35,5 @@ export type UseFieldOptions< export type UseFormReturnType = TFormValidator extends | Validator | undefined - ? ReturnType> + ? ReactFormExtendedApi : never From f0df80464039a65d9719353fd10699fbb23729d6 Mon Sep 17 00:00:00 2001 From: Harry Whorlow Date: Mon, 6 Jan 2025 08:50:04 +0100 Subject: [PATCH 6/6] fix: format demo import --- docs/framework/react/guides/reusable-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/framework/react/guides/reusable-fields.md b/docs/framework/react/guides/reusable-fields.md index ec1b12005..39ab64571 100644 --- a/docs/framework/react/guides/reusable-fields.md +++ b/docs/framework/react/guides/reusable-fields.md @@ -10,7 +10,7 @@ As TanStack form is a headless library, we provide you the core building blocks To create a reusable fields, you can do the following. ```tsx -import { InferValidFormKeys, UseFormReturnType } from '@tanstack/react-form'; +import { InferValidFormKeys, UseFormReturnType } from '@tanstack/react-form'; export default function GenericTextField< TForm,