Skip to content

Commit

Permalink
fix: remove _TData abstraction (#465)
Browse files Browse the repository at this point in the history
* fix: remove _TData abstraction

* tests: fix
  • Loading branch information
tannerlinsley authored Sep 14, 2023
1 parent 9e5fef4 commit 638a391
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 152 deletions.
96 changes: 34 additions & 62 deletions packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,21 @@ import { Store } from '@tanstack/store'

export type ValidationCause = 'change' | 'blur' | 'submit' | 'mount'

type ValidateFn<TData, TParentData, TName extends DeepKeys<TParentData>> = (
type ValidateFn<TParentData, TName extends DeepKeys<TParentData>, TData> = (
value: TData,
fieldApi: FieldApi<TData, TParentData, TName>,
fieldApi: FieldApi<TParentData, TName>,
) => ValidationError

type ValidateAsyncFn<
TData,
TParentData,
TName extends DeepKeys<TParentData>,
TData,
> = (
value: TData,
fieldApi: FieldApi<TData, TParentData, TName>,
fieldApi: FieldApi<TParentData, TName>,
) => ValidationError | Promise<ValidationError>

export interface FieldOptions<
TData,
TParentData,
/**
* This allows us to restrict the name to only be a valid field name while
Expand All @@ -29,41 +28,29 @@ export interface FieldOptions<
/**
* If TData is unknown, we can use the TName generic to determine the type
*/
TResolvedData = unknown extends TData ? DeepValue<TParentData, TName> : TData,
TData = DeepValue<TParentData, TName>,
> {
name: DeepKeys<TParentData>
index?: TResolvedData extends any[] ? number : never
defaultValue?: TResolvedData
index?: TData extends any[] ? number : never
defaultValue?: TData
asyncDebounceMs?: number
asyncAlways?: boolean
onMount?: (formApi: FieldApi<TResolvedData, TParentData, TName>) => void
onChange?: ValidateFn<TResolvedData, TParentData, TName>
onChangeAsync?: ValidateAsyncFn<TResolvedData, TParentData, TName>
onMount?: (formApi: FieldApi<TParentData, TName>) => void
onChange?: ValidateFn<TParentData, TName, TData>
onChangeAsync?: ValidateAsyncFn<TParentData, TName, TData>
onChangeAsyncDebounceMs?: number
onBlur?: ValidateFn<TResolvedData, TParentData, TName>
onBlurAsync?: ValidateAsyncFn<TResolvedData, TParentData, TName>
onBlur?: ValidateFn<TParentData, TName, TData>
onBlurAsync?: ValidateAsyncFn<TParentData, TName, TData>
onBlurAsyncDebounceMs?: number
onSubmitAsync?: ValidateAsyncFn<TResolvedData, TParentData, TName>
onSubmitAsync?: ValidateAsyncFn<TParentData, TName, TData>
defaultMeta?: Partial<FieldMeta>
}

export interface FieldApiOptions<
TData,
TParentData,
/**
* This allows us to restrict the name to only be a valid field name while
* also assigning it to a generic
*/
TName extends DeepKeys<TParentData>,
/**
* If TData is unknown, we can use the TName generic to determine the type
*/
TResolvedData extends ResolveData<TData, TParentData, TName> = ResolveData<
TData,
TParentData,
TName
>,
> extends FieldOptions<TData, TParentData, TName, TResolvedData> {
TData = DeepValue<TParentData, TName>,
> extends FieldOptions<TParentData, TName, TData> {
form: FormApi<TParentData>
}

Expand All @@ -82,34 +69,25 @@ export type FieldState<TData> = {
meta: FieldMeta
}

export type ResolveData<TData, TParentData, TName> = unknown extends TData
? DeepValue<TParentData, TName>
: TData

export type ResolveName<TParentData> = unknown extends TParentData
? string
: DeepKeys<TParentData>

export class FieldApi<
TData,
TParentData,
TName extends DeepKeys<TParentData>,
TResolvedData extends ResolveData<TData, TParentData, TName> = ResolveData<
TData,
TParentData,
TName
>,
TData = DeepValue<TParentData, TName>,
> {
uid: number
form: FieldApiOptions<TData, TParentData, TName, TResolvedData>['form']
form: FieldApiOptions<TParentData, TName, TData>['form']
name!: DeepKeys<TParentData>
options: FieldApiOptions<TData, TParentData, TName> = {} as any
store!: Store<FieldState<TResolvedData>>
state!: FieldState<TResolvedData>
prevState!: FieldState<TResolvedData>
options: FieldApiOptions<TParentData, TName> = {} as any
store!: Store<FieldState<TData>>
state!: FieldState<TData>
prevState!: FieldState<TData>

constructor(
opts: FieldApiOptions<TData, TParentData, TName, TResolvedData> & {
opts: FieldApiOptions<TParentData, TName, TData> & {
form: FormApi<TParentData>
},
) {
Expand All @@ -123,7 +101,7 @@ export class FieldApi<

this.name = opts.name as any

this.store = new Store<FieldState<TResolvedData>>(
this.store = new Store<FieldState<TData>>(
{
value: this.getValue(),
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
Expand Down Expand Up @@ -190,7 +168,7 @@ export class FieldApi<
}
}

update = (opts: FieldApiOptions<TResolvedData, TParentData, TName>) => {
update = (opts: FieldApiOptions<TParentData, TName, TData>) => {
// Default Value
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (this.state.value === undefined) {
Expand All @@ -212,12 +190,12 @@ export class FieldApi<
this.options = opts as never
}

getValue = (): TResolvedData => {
getValue = (): TData => {
return this.form.getFieldValue(this.name) as any
}

setValue = (
updater: Updater<TResolvedData>,
updater: Updater<TData>,
options?: { touch?: boolean; notify?: boolean },
) => {
this.form.setFieldValue(this.name, updater as never, options)
Expand All @@ -241,13 +219,12 @@ export class FieldApi<

getInfo = () => this.form.getFieldInfo(this.name)

pushValue = (
value: TResolvedData extends any[] ? TResolvedData[number] : never,
) => this.form.pushFieldValue(this.name, value as any)
pushValue = (value: TData extends any[] ? TData[number] : never) =>
this.form.pushFieldValue(this.name, value as any)

insertValue = (
index: number,
value: TResolvedData extends any[] ? TResolvedData[number] : never,
value: TData extends any[] ? TData[number] : never,
) => this.form.insertFieldValue(this.name, index, value as any)

removeValue = (index: number) => this.form.removeFieldValue(this.name, index)
Expand All @@ -256,16 +233,11 @@ export class FieldApi<
this.form.swapFieldValues(this.name, aIndex, bIndex)

getSubField = <
TSubData,
TSubName extends DeepKeys<TResolvedData>,
TSubResolvedData extends ResolveData<
DeepValue<TResolvedData, TSubName>,
TResolvedData,
TSubName
>,
TSubName extends DeepKeys<TData>,
TSubData = DeepValue<TData, TSubName>,
>(
name: TSubName,
): FieldApi<TSubData, TResolvedData, TSubName, TSubResolvedData> =>
): FieldApi<TData, TSubName, TSubData> =>
new FieldApi({
name: `${this.name}.${name}` as never,
form: this.form,
Expand Down Expand Up @@ -398,7 +370,7 @@ export class FieldApi<

validate = (
cause: ValidationCause,
value?: TResolvedData,
value?: TData,
): ValidationError[] | Promise<ValidationError[]> => {
// If the field is pristine and validatePristine is false, do not validate
if (!this.state.meta.isTouched) return []
Expand All @@ -416,7 +388,7 @@ export class FieldApi<
return this.validateAsync(value, cause)
}

handleChange = (updater: Updater<TResolvedData>) => {
handleChange = (updater: Updater<TData>) => {
this.setValue(updater, { touch: true })
}

Expand Down
2 changes: 1 addition & 1 deletion packages/form-core/src/FormApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type FormOptions<TData> = {
}

export type FieldInfo<TFormData> = {
instances: Record<string, FieldApi<any, TFormData, any>>
instances: Record<string, FieldApi<TFormData, any, any>>
} & ValidationMeta

export type ValidationMeta = {
Expand Down
6 changes: 3 additions & 3 deletions packages/react-form/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { FieldOptions, DeepKeys } from '@tanstack/form-core'
import type { FieldOptions, DeepKeys, DeepValue } from '@tanstack/form-core'

export type UseFieldOptions<
TData,
TParentData,
TName extends DeepKeys<TParentData>,
> = FieldOptions<TData, TParentData, TName> & {
TData = DeepValue<TParentData, TName>,
> = FieldOptions<TParentData, TName, TData> & {
mode?: 'value' | 'array'
}
55 changes: 14 additions & 41 deletions packages/react-form/src/useField.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import React, { useState } from 'react'
import { useStore } from '@tanstack/react-store'
import type {
DeepKeys,
DeepValue,
Narrow,
ResolveData,
} from '@tanstack/form-core'
import type { DeepKeys, DeepValue, Narrow } from '@tanstack/form-core'
import { FieldApi, functionalUpdate } from '@tanstack/form-core'
import { useFormContext, formContext } from './formContext'
import useIsomorphicLayoutEffect from 'use-isomorphic-layout-effect'
Expand All @@ -14,35 +9,21 @@ import type { UseFieldOptions } from './types'
declare module '@tanstack/form-core' {
// eslint-disable-next-line no-shadow
interface FieldApi<
TData,
TParentData,
TName extends DeepKeys<TParentData>,
TResolvedData extends ResolveData<TData, TParentData, TName> = ResolveData<
TData,
TParentData,
TName
>,
TData = DeepValue<TParentData, TName>,
> {
Field: FieldComponent<TData>
}
}

export type UseField<TParentData> = <TName extends DeepKeys<TParentData>>(
opts?: { name: Narrow<TName> } & UseFieldOptions<
DeepValue<TParentData, TName>,
TParentData,
TName
>,
) => FieldApi<DeepValue<TParentData, TName>, TParentData, TName>
opts?: { name: Narrow<TName> } & UseFieldOptions<TParentData, TName>,
) => FieldApi<TParentData, TName, DeepValue<TParentData, TName>>

export function useField<
TData,
TParentData,
TName extends DeepKeys<TParentData>,
>(
opts: UseFieldOptions<TData, TParentData, TName>,
export function useField<TParentData, TName extends DeepKeys<TParentData>>(
opts: UseFieldOptions<TParentData, TName>,
): FieldApi<
TData,
TParentData,
TName
// Omit<typeof opts, 'onMount'> & {
Expand Down Expand Up @@ -95,14 +76,11 @@ export function useField<
}

type FieldComponentProps<
TData,
TParentData,
TName extends DeepKeys<TParentData>,
TResolvedData extends ResolveData<TData, TParentData, TName>,
TData = DeepValue<TParentData, TName>,
> = {
children: (
fieldApi: FieldApi<TData, TParentData, TName, TResolvedData>,
) => any
children: (fieldApi: FieldApi<TParentData, TName, TData>) => any
} & (TParentData extends any[]
? {
name?: TName
Expand All @@ -112,27 +90,22 @@ type FieldComponentProps<
name: TName
index?: never
}) &
Omit<UseFieldOptions<TData, TParentData, TName>, 'name' | 'index'>
Omit<UseFieldOptions<TParentData, TName>, 'name' | 'index'>

export type FieldComponent<TParentData> = <
TData,
TName extends DeepKeys<TParentData>,
TResolvedData extends ResolveData<TData, TParentData, TName> = ResolveData<
TData,
TParentData,
TName
>,
TData = DeepValue<TParentData, TName>,
>({
children,
...fieldOptions
}: FieldComponentProps<TData, TParentData, TName, TResolvedData>) => any
}: FieldComponentProps<TParentData, TName, TData>) => any

export function Field<TData, TParentData, TName extends DeepKeys<TParentData>>({
export function Field<TParentData, TName extends DeepKeys<TParentData>>({
children,
...fieldOptions
}: {
children: (fieldApi: FieldApi<TData, TParentData, TName>) => any
} & UseFieldOptions<TData, TParentData, TName>) {
children: (fieldApi: FieldApi<TParentData, TName>) => any
} & UseFieldOptions<TParentData, TName>) {
const fieldApi = useField(fieldOptions as any)

return (
Expand Down
10 changes: 5 additions & 5 deletions packages/vue-form/src/tests/useField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('useField', () => {

return () => (
<form.Field name="firstName" defaultValue="FirstName">
{({ field }: { field: FieldApi<string, Person> }) => (
{({ field }: { field: FieldApi<Person, 'firstName'> }) => (
<input
data-testid={'fieldinput'}
value={field.state.value}
Expand Down Expand Up @@ -68,7 +68,7 @@ describe('useField', () => {
name="firstName"
onChange={(value) => (value === 'other' ? error : undefined)}
>
{({ field }: { field: FieldApi<string, Person> }) => (
{({ field }: { field: FieldApi<Person, 'firstName'> }) => (
<div>
<input
data-testid="fieldinput"
Expand Down Expand Up @@ -111,7 +111,7 @@ describe('useField', () => {
name="firstName"
onChange={(value) => (value === 'other' ? error : undefined)}
>
{({ field }: { field: FieldApi<string, Person> }) => (
{({ field }: { field: FieldApi<Person, 'firstName'> }) => (
<div>
<input
data-testid="fieldinput"
Expand Down Expand Up @@ -159,7 +159,7 @@ describe('useField', () => {
return error
}}
>
{({ field }: { field: FieldApi<string, Person> }) => (
{({ field }: { field: FieldApi<Person, 'firstName'> }) => (
<div>
<input
data-testid="fieldinput"
Expand Down Expand Up @@ -211,7 +211,7 @@ describe('useField', () => {
return error
}}
>
{({ field }: { field: FieldApi<string, Person> }) => (
{({ field }: { field: FieldApi<Person, 'firstName'> }) => (
<div>
<input
data-testid="fieldinput"
Expand Down
Loading

0 comments on commit 638a391

Please sign in to comment.