Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
162 changes: 134 additions & 28 deletions packages/form-core/src/FieldApi.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Derived, batch } from '@tanstack/store'
import { Derived, Store, batch } from '@tanstack/store'
import {
isStandardSchemaValidator,
standardSchemaValidators,
} from './standardSchemaValidator'
import { defaultFieldMeta } from './metaHelper'
import {
determineFieldLevelErrorSourceAndValue,
evaluate,
getAsyncValidatorArray,
getBy,
getSyncValidatorArray,
Expand Down Expand Up @@ -864,6 +865,58 @@ export type AnyFieldMeta = FieldMeta<
any
>

export type FieldBaseState<
TParentData,
TName extends DeepKeys<TParentData>,
TData extends DeepValue<TParentData, TName>,
TOnMount extends undefined | FieldValidateOrFn<TParentData, TName, TData>,
TOnChange extends undefined | FieldValidateOrFn<TParentData, TName, TData>,
TOnChangeAsync extends
| undefined
| FieldAsyncValidateOrFn<TParentData, TName, TData>,
TOnBlur extends undefined | FieldValidateOrFn<TParentData, TName, TData>,
TOnBlurAsync extends
| undefined
| FieldAsyncValidateOrFn<TParentData, TName, TData>,
TOnSubmit extends undefined | FieldValidateOrFn<TParentData, TName, TData>,
TOnSubmitAsync extends
| undefined
| FieldAsyncValidateOrFn<TParentData, TName, TData>,
TOnDynamic extends undefined | FieldValidateOrFn<TParentData, TName, TData>,
TOnDynamicAsync extends
| undefined
| FieldAsyncValidateOrFn<TParentData, TName, TData>,
> = {
name: TName
defaultMeta:
| Partial<
FieldMeta<
TParentData,
TName,
TData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
any,
any,
any,
any,
any,
any,
any,
any,
any
>
>
| undefined
}

/**
* An object type representing the state of a field.
*/
Expand Down Expand Up @@ -1049,10 +1102,31 @@ export class FieldApi<
TFormOnServer,
TParentSubmitMeta
>['form']

baseStore: Store<
FieldBaseState<
TParentData,
TName,
TData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync
>
>

/**
* The field name.
*/
name!: DeepKeys<TParentData>
get name(): DeepKeys<TParentData> {
return this.baseStore.state.name
}

/**
* The field options.
*/
Expand Down Expand Up @@ -1152,20 +1226,42 @@ export class FieldApi<
>,
) {
this.form = opts.form as never
this.name = opts.name as never
this.timeoutIds = {
validations: {} as Record<ValidationCause, never>,
listeners: {} as Record<ListenerCause, never>,
formListeners: {} as Record<ListenerCause, never>,
}

this.baseStore = new Store<
FieldBaseState<
TParentData,
TName,
TData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync
>
>({
name: opts.name,
defaultMeta: opts.defaultMeta,
})

this.store = new Derived({
deps: [this.form.store],
fn: () => {
const value = this.form.getFieldValue(this.name)
const meta = this.form.getFieldMeta(this.name) ?? {
deps: [this.form.store, this.baseStore],
fn: ({ currDepVals }) => {
const fieldName = currDepVals[1].name
const defaultMeta = currDepVals[1].defaultMeta

const value = this.form.getFieldValue(fieldName)
const meta = this.form.getFieldMeta(fieldName) ?? {
...defaultFieldMeta,
...opts.defaultMeta,
...(defaultMeta ?? {}),
}

return {
Expand Down Expand Up @@ -1232,11 +1328,14 @@ export class FieldApi<
mount = () => {
const cleanup = this.store.mount()

if ((this.options.defaultValue as unknown) !== undefined) {
this.form.setFieldValue(this.name, this.options.defaultValue as never, {
if (this.options.defaultValue !== undefined) {
this.form.setFieldValue(this.name, this.options.defaultValue, {
dontUpdateMeta: true,
})
}
if (this.options.name !== this.name) {
this.baseStore.setState((prev) => ({ ...prev, name: this.options.name }))
}

const info = this.getInfo()
info.instance = this as never
Expand Down Expand Up @@ -1309,34 +1408,41 @@ export class FieldApi<
TParentSubmitMeta
>,
) => {
this.options = opts as never
const shouldUpdateValue =
opts.defaultValue !== undefined &&
!this.state.meta.isTouched &&
!evaluate(opts.defaultValue, this.options.defaultValue)

const nameHasChanged = this.name !== opts.name
this.name = opts.name
const shouldUpdateName = !evaluate(this.name, opts.name)

// Default Value
if ((this.state.value as unknown) === undefined) {
const formDefault = getBy(opts.form.options.defaultValues, opts.name)
const shouldUpdateMeta = !evaluate(
this.baseStore.state.defaultMeta,
opts.defaultMeta,
)

const defaultValue = (opts.defaultValue as unknown) ?? formDefault
if (shouldUpdateValue) {
this.form.setFieldValue(this.name, this.options.defaultValue!, {
dontUpdateMeta: true,
})
}

// The name is dynamic in array fields. It changes when the user performs operations like removing or reordering.
// In this case, we don't want to force a default value if the store managed to find an existing value.
if (nameHasChanged) {
this.setValue((val) => (val as unknown) || defaultValue, {
dontUpdateMeta: true,
})
} else if (defaultValue !== undefined) {
this.setValue(defaultValue as never, {
dontUpdateMeta: true,
})
}
if (shouldUpdateName) {
this.baseStore.setState((prev) => ({ ...prev, name: opts.name }))
}

if (shouldUpdateMeta) {
this.baseStore.setState((prev) => ({
...prev,
defaultMeta: opts.defaultMeta,
}))
}

// Default Meta
if (this.form.getFieldMeta(this.name) === undefined) {
this.setMeta(this.state.meta)
}

this.options = opts as never
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/form-core/src/FieldGroupApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,14 +283,14 @@ export class FieldGroupApi<
let values: TFieldGroupData
if (typeof this.fieldsMap === 'string') {
// all values live at that name, so we can directly fetch it
values = getBy(currFormStore.values, this.fieldsMap)
values = getBy(currFormStore.values, this.fieldsMap).value
} else {
// we need to fetch the values from all places where they were mapped from
values = {} as never
const fields: Record<keyof TFieldGroupData, string> = this
.fieldsMap as never
for (const key in fields) {
values[key] = getBy(currFormStore.values, fields[key])
values[key] = getBy(currFormStore.values, fields[key]).value
}
}

Expand Down
20 changes: 12 additions & 8 deletions packages/form-core/src/FormApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1051,7 +1051,7 @@ export class FormApi<
const prevFieldInfo =
prevVal?.[fieldName as never as keyof typeof prevVal]

const curFieldVal = getBy(currBaseStore.values, fieldName)
const curFieldVal = getBy(currBaseStore.values, fieldName).value

let fieldErrors = prevFieldInfo?.errors
if (
Expand Down Expand Up @@ -1079,7 +1079,7 @@ export class FormApi<
const isDefaultValue =
evaluate(
curFieldVal,
getBy(this.options.defaultValues, fieldName),
getBy(this.options.defaultValues, fieldName).value,
) ||
evaluate(
curFieldVal,
Expand Down Expand Up @@ -2131,7 +2131,7 @@ export class FormApi<
*/
getFieldValue = <TField extends DeepKeys<TFormData>>(
field: TField,
): DeepValue<TFormData, TField> => getBy(this.state.values, field)
): DeepValue<TFormData, TField> => getBy(this.state.values, field).value

/**
* Gets the metadata of the specified field.
Expand Down Expand Up @@ -2304,7 +2304,7 @@ export class FormApi<
await this.validateField(field, 'change')

// Shift down all meta after validating to make sure the new field has been mounted
metaHelper(this).handleArrayFieldMetaShift(field, index, 'insert')
metaHelper(this).handleArrayInsert(field, index)

await this.validateArrayFieldsStartingFrom(field, index, 'change')
}
Expand Down Expand Up @@ -2360,7 +2360,7 @@ export class FormApi<
)

// Shift up all meta
metaHelper(this).handleArrayFieldMetaShift(field, index, 'remove')
metaHelper(this).handleArrayRemove(field, index)

if (lastIndex !== null) {
const start = `${field}[${lastIndex}]`
Expand Down Expand Up @@ -2392,7 +2392,7 @@ export class FormApi<
)

// Swap meta
metaHelper(this).handleArrayFieldMetaShift(field, index1, 'swap', index2)
metaHelper(this).handleArraySwap(field, index1, index2)

// Validate the whole array
this.validateField(field, 'change')
Expand Down Expand Up @@ -2421,7 +2421,7 @@ export class FormApi<
)

// Move meta between index1 and index2
metaHelper(this).handleArrayFieldMetaShift(field, index1, 'move', index2)
metaHelper(this).handleArrayMove(field, index1, index2)

// Validate the whole array
this.validateField(field, 'change')
Expand Down Expand Up @@ -2472,7 +2472,11 @@ export class FormApi<
[field]: defaultFieldMeta,
},
values: this.options.defaultValues
? setBy(prev.values, field, getBy(this.options.defaultValues, field))
? setBy(
prev.values,
field,
getBy(this.options.defaultValues, field).value,
)
: prev.values,
}
})
Expand Down
Loading
Loading