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

[WIP] [FEAT] Allow returning anything in a validator, not just a string #1104

Draft
wants to merge 46 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
44a71cf
chore: WIP adding in inferencing to validator's return type
crutchcorn Jan 9, 2025
d9ef915
chore: more work on fixing types
crutchcorn Jan 9, 2025
8b87404
chore: wip on things more
crutchcorn Jan 9, 2025
d2544c7
chore: fix issues with FormApi and FieldApi
crutchcorn Jan 9, 2025
9c1caef
chore: add more fields to infer
crutchcorn Jan 9, 2025
901efb3
chore: change typed from unknown to undefined
crutchcorn Jan 9, 2025
9b18dbd
chore: more fixes
crutchcorn Jan 9, 2025
97767f4
chore: add some new type tests
crutchcorn Jan 9, 2025
5263c16
chore: add more type tests
crutchcorn Jan 9, 2025
e321a09
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 9, 2025
05e3e99
chore: migrate React components to use new prop types
crutchcorn Jan 9, 2025
00963bd
chore: move useTransform hook to new types
crutchcorn Jan 9, 2025
be575af
chore: add convinience type for AnyFormAPI and AnyFieldAPI
crutchcorn Jan 9, 2025
f2e0d00
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 9, 2025
6de25f3
chore: fix NextJS and Remix adapters
crutchcorn Jan 9, 2025
65c31b6
chore: fix build of Start adapter
crutchcorn Jan 9, 2025
e902f69
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 9, 2025
0035012
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 9, 2025
64cfbe0
chore: fix issues with Nx on Windows
crutchcorn Jan 10, 2025
0c7cdbe
chore: fix issues with FieldAny in examples
crutchcorn Jan 10, 2025
d3758c0
chore: upgrade React packages to stable
crutchcorn Jan 10, 2025
1a196a5
chore: WIP attempt to fix Start package
crutchcorn Jan 10, 2025
29c25b3
chore: fix mergeForm typing
crutchcorn Jan 10, 2025
b91fc72
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 10, 2025
5d21496
chore: migrate Angular adapter to use new API
crutchcorn Jan 10, 2025
035ae35
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 10, 2025
70a644c
chore: migrate Lit adapter to new generics
crutchcorn Jan 11, 2025
eb942ec
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 11, 2025
98fd587
chore: attempt 1 at fixing Vue's types
crutchcorn Jan 11, 2025
074f9a4
chore: fix Vue types for JSX and Vue SFCs alike
crutchcorn Jan 11, 2025
2e9f16d
chore: migrate Vue type to use new generics
crutchcorn Jan 11, 2025
ae2609e
chore: fix Vue test types
crutchcorn Jan 11, 2025
5600797
chore: fix Vue examples
crutchcorn Jan 11, 2025
af991ec
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 11, 2025
4604b41
chore: migrate Solid to new generics system
crutchcorn Jan 11, 2025
ac698c8
chore: fix Solid examples
crutchcorn Jan 11, 2025
c9ea6cf
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 11, 2025
69c1897
ci: apply automated fixes and generate docs (attempt 2/3)
autofix-ci[bot] Jan 11, 2025
1798a88
chore: fix issues with Solid library
crutchcorn Jan 12, 2025
4138729
chore: fix knip
crutchcorn Jan 12, 2025
1b46420
chore: fix issues with error casting
crutchcorn Jan 12, 2025
56f813a
chore: fix issue with ESLint
crutchcorn Jan 12, 2025
a5d41db
docs: show errorMap and errorarray
crutchcorn Jan 12, 2025
57198cb
Merge branch 'main' into return-anything-not-just-string
crutchcorn Jan 15, 2025
dcd8e3b
chore: upgrade all deps
crutchcorn Jan 15, 2025
0d40105
ci: apply automated fixes and generate docs
autofix-ci[bot] Jan 15, 2025
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
Prev Previous commit
Next Next commit
chore: migrate React components to use new prop types
crutchcorn committed Jan 11, 2025
commit 05e3e997305b17dc46d7b97a3bf7db67d2db7ea3
16 changes: 15 additions & 1 deletion packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
@@ -936,7 +936,21 @@ export class FieldApi<
TName,
TFieldValidator,
TFormValidator,
TData
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>,
) => {
// Default Value
30 changes: 29 additions & 1 deletion packages/react-form/src/types.ts
Original file line number Diff line number Diff line change
@@ -19,12 +19,40 @@ export type UseFieldOptions<
| Validator<TParentData, unknown>
| undefined = undefined,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
TFormOnMountReturn = undefined,
TFormOnChangeReturn = undefined,
TFormOnChangeAsyncReturn = undefined,
TFormOnBlurReturn = undefined,
TFormOnBlurAsyncReturn = undefined,
TFormOnSubmitReturn = undefined,
TFormOnSubmitAsyncReturn = undefined,
> = FieldApiOptions<
TParentData,
TName,
TFieldValidator,
TFormValidator,
TData
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
> & {
mode?: 'value' | 'array'
}
243 changes: 234 additions & 9 deletions packages/react-form/src/useField.tsx
Original file line number Diff line number Diff line change
@@ -11,11 +11,28 @@ interface ReactFieldApi<
TFormValidator extends
| Validator<TParentData, unknown>
| undefined = undefined,
TFormOnMountReturn = undefined,
TFormOnChangeReturn = undefined,
TFormOnChangeAsyncReturn = undefined,
TFormOnBlurReturn = undefined,
TFormOnBlurAsyncReturn = undefined,
TFormOnSubmitReturn = undefined,
TFormOnSubmitAsyncReturn = undefined,
> {
/**
* A pre-bound and type-safe sub-field component using this field as a root.
*/
Field: FieldComponent<TParentData, TFormValidator>
Field: FieldComponent<
TParentData,
TFormValidator,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>
}

/**
@@ -28,18 +45,72 @@ export type UseField<
TFormValidator extends
| Validator<TParentData, unknown>
| undefined = undefined,
TFormOnMountReturn = undefined,
TFormOnChangeReturn = undefined,
TFormOnChangeAsyncReturn = undefined,
TFormOnBlurReturn = undefined,
TFormOnBlurAsyncReturn = undefined,
TFormOnSubmitReturn = undefined,
TFormOnSubmitAsyncReturn = undefined,
> = <
TName extends DeepKeys<TParentData>,
TFieldValidator extends
| Validator<DeepValue<TParentData, TName>, unknown>
| undefined = undefined,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
>(
opts: Omit<
UseFieldOptions<TParentData, TName, TFieldValidator, TFormValidator, TData>,
UseFieldOptions<
TParentData,
TName,
TFieldValidator,
TFormValidator,
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>,
'form'
>,
) => FieldApi<TParentData, TName, TFieldValidator, TFormValidator, TData>
) => FieldApi<
TParentData,
TName,
TFieldValidator,
TFormValidator,
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>

/**
* A hook for managing a field in a form.
@@ -57,13 +128,41 @@ export function useField<
| Validator<TParentData, unknown>
| undefined = undefined,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
TFormOnMountReturn = undefined,
TFormOnChangeReturn = undefined,
TFormOnChangeAsyncReturn = undefined,
TFormOnBlurReturn = undefined,
TFormOnBlurAsyncReturn = undefined,
TFormOnSubmitReturn = undefined,
TFormOnSubmitAsyncReturn = undefined,
>(
opts: UseFieldOptions<
TParentData,
TName,
TFieldValidator,
TFormValidator,
TData
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>,
) {
const [fieldApi] = useState(() => {
@@ -116,17 +215,65 @@ type FieldComponentProps<
| Validator<TParentData, unknown>
| undefined = undefined,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
TFormOnMountReturn = undefined,
TFormOnChangeReturn = undefined,
TFormOnChangeAsyncReturn = undefined,
TFormOnBlurReturn = undefined,
TFormOnBlurAsyncReturn = undefined,
TFormOnSubmitReturn = undefined,
TFormOnSubmitAsyncReturn = undefined,
> = {
children: (
fieldApi: FieldApi<
TParentData,
TName,
TFieldValidator,
TFormValidator,
TData
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>,
) => ReactNode
} & UseFieldOptions<TParentData, TName, TFieldValidator, TFormValidator, TData>
} & UseFieldOptions<
TParentData,
TName,
TFieldValidator,
TFormValidator,
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>

/**
* A type alias representing a field component for a specific form data type.
@@ -136,12 +283,26 @@ export type FieldComponent<
TFormValidator extends
| Validator<TParentData, unknown>
| undefined = undefined,
TFormOnMountReturn = undefined,
TFormOnChangeReturn = undefined,
TFormOnChangeAsyncReturn = undefined,
TFormOnBlurReturn = undefined,
TFormOnBlurAsyncReturn = undefined,
TFormOnSubmitReturn = undefined,
TFormOnSubmitAsyncReturn = undefined,
> = <
TName extends DeepKeys<TParentData>,
TFieldValidator extends
| Validator<DeepValue<TParentData, TName>, unknown>
| undefined = undefined,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
>({
children,
...fieldOptions
@@ -151,7 +312,21 @@ export type FieldComponent<
TName,
TFieldValidator,
TFormValidator,
TData
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>,
'form'
>) => ReactNode
@@ -171,6 +346,20 @@ export const Field = (<
| Validator<TParentData, unknown>
| undefined = undefined,
TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
TFormOnMountReturn = undefined,
TFormOnChangeReturn = undefined,
TFormOnChangeAsyncReturn = undefined,
TFormOnBlurReturn = undefined,
TFormOnBlurAsyncReturn = undefined,
TFormOnSubmitReturn = undefined,
TFormOnSubmitAsyncReturn = undefined,
>({
children,
...fieldOptions
@@ -179,7 +368,21 @@ export const Field = (<
TName,
TFieldValidator,
TFormValidator,
TData
TData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn,
TFormOnMountReturn,
TFormOnChangeReturn,
TFormOnChangeAsyncReturn,
TFormOnBlurReturn,
TFormOnBlurAsyncReturn,
TFormOnSubmitReturn,
TFormOnSubmitAsyncReturn
>): ReactNode => {
const fieldApi = useField(fieldOptions as any)

@@ -194,4 +397,26 @@ export const Field = (<
[children, fieldApi, fieldApi.state.value, fieldApi.state.meta],
)
return (<>{jsxToDisplay}</>) as never
}) satisfies FunctionComponent<FieldComponentProps<any, any, any, any, any>>
}) satisfies FunctionComponent<
FieldComponentProps<
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any,
any
>
>
131 changes: 121 additions & 10 deletions packages/react-form/src/useForm.tsx
Original file line number Diff line number Diff line change
@@ -14,16 +14,59 @@ import type { FormOptions, FormState, Validator } from '@tanstack/form-core'
export interface ReactFormApi<
TFormData,
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
> {
/**
* A React component to render form fields. With this, you can render and manage individual form fields.
*/
Field: FieldComponent<TFormData, TFormValidator>
Field: FieldComponent<
TFormData,
TFormValidator,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn
>
/**
* A `Subscribe` function that allows you to listen and react to changes in the form's state. It's especially useful when you need to execute side effects or render specific components in response to state updates.
*/
Subscribe: <TSelected = NoInfer<FormState<TFormData>>>(props: {
selector?: (state: NoInfer<FormState<TFormData>>) => TSelected
Subscribe: <
TSelected = NoInfer<
FormState<
TFormData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn
>
>,
>(props: {
selector?: (
state: NoInfer<
FormState<
TFormData,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn
>
>,
) => TSelected
children: ((state: NoInfer<TSelected>) => ReactNode) | ReactNode
}) => ReactNode
}
@@ -34,15 +77,45 @@ export interface ReactFormApi<
export type ReactFormExtendedApi<
TFormData,
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
> = FormApi<TFormData, TFormValidator> & ReactFormApi<TFormData, TFormValidator>
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
> = FormApi<
TFormData,
TFormValidator,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn
> &
ReactFormApi<
TFormData,
TFormValidator,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn
>

function LocalSubscribe({
form,
selector,
children,
}: PropsWithChildren<{
form: FormApi<any, any>
selector: (state: FormState<any>) => FormState<any>
form: FormApi<any, any, any, any, any, any, any, any, any>
selector: (
state: FormState<any, any, any, any, any, any, any, any>,
) => FormState<any, any, any, any, any, any, any, any>
}>) {
const data = useStore(form.store, selector)

@@ -57,12 +130,50 @@ function LocalSubscribe({
export function useForm<
TFormData,
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,
>(opts?: FormOptions<TFormData, TFormValidator>) {
TOnMountReturn = undefined,
TOnChangeReturn = undefined,
TOnChangeAsyncReturn = undefined,
TOnBlurReturn = undefined,
TOnBlurAsyncReturn = undefined,
TOnSubmitReturn = undefined,
TOnSubmitAsyncReturn = undefined,
>(
opts?: FormOptions<
TFormData,
TFormValidator,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn
>,
) {
const [formApi] = useState(() => {
const api = new FormApi<TFormData, TFormValidator>(opts)
const api = new FormApi<
TFormData,
TFormValidator,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn
>(opts)

const extendedApi: ReactFormExtendedApi<TFormData, TFormValidator> =
api as never
const extendedApi: ReactFormExtendedApi<
TFormData,
TFormValidator,
TOnMountReturn,
TOnChangeReturn,
TOnChangeAsyncReturn,
TOnBlurReturn,
TOnBlurAsyncReturn,
TOnSubmitReturn,
TOnSubmitAsyncReturn
> = api as never
extendedApi.Field = function APIField(props) {
return <Field {...props} form={api} />
}