diff --git a/packages/form-core/src/utils.ts b/packages/form-core/src/utils.ts index 788c7c14b..38b888fa1 100644 --- a/packages/form-core/src/utils.ts +++ b/packages/form-core/src/utils.ts @@ -43,6 +43,20 @@ export function getBy(obj: unknown, path: string | (string | number)[]): any { }, obj) } +/** + * Check if an object is a File. + * @private + */ +export function isFile(obj: any) { + return ( + obj && + typeof obj === 'object' && + 'name' in obj && + 'size' in obj && + 'type' in obj + ) +} + /** * Set a value on an object using a path, including dot notation. * @private @@ -52,7 +66,10 @@ export function setBy(obj: any, _path: any, updater: Updater) { function doSet(parent?: any): any { if (!path.length) { - return functionalUpdate(updater, parent) + return functionalUpdate( + isFile(updater) ? { file: updater, uuid: uuid() } : updater, + parent, + ) } const key = path.shift() @@ -422,6 +439,15 @@ export const isGlobalFormValidationError = ( } export function evaluate(objA: T, objB: T) { + if (objA instanceof File && objB instanceof File) { + return ( + objA.name === objB.name && + objA.size === objB.size && + objA.type === objB.type && + objA.lastModified === objB.lastModified + ) + } + if (Object.is(objA, objB)) { return true } diff --git a/packages/form-core/tests/FormApi.spec.ts b/packages/form-core/tests/FormApi.spec.ts index dd418d727..fb0c3bf8b 100644 --- a/packages/form-core/tests/FormApi.spec.ts +++ b/packages/form-core/tests/FormApi.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest' import { z } from 'zod' -import { FieldApi, FormApi, formEventClient } from '../src/index' +import { FieldApi, FormApi, isFile } from '../src/index' import { sleep } from './utils' import type { AnyFieldApi, AnyFormApi } from '../src/index' @@ -4096,6 +4096,37 @@ it('should generate a formId if not provided', () => { expect(form.formId.length).toBeGreaterThan(1) }) +it('should set a file value with uuid when setting a field value with File', () => { + const form = new FormApi({ + defaultValues: { + avatar: undefined, + } as { avatar: unknown }, + }) + + form.mount() + + const firstFile = new File(['first'], 'first.png', { type: 'image/png' }) + form.setFieldValue('avatar', firstFile) + + const firstValue = form.state.values.avatar as { file: File; uuid: string } + + expect(firstValue).toBeDefined() + expect(isFile(firstValue.file)).toBe(true) + expect(firstValue.file.name).toBe('first.png') + expect(typeof firstValue.uuid).toBe('string') + expect(firstValue.uuid.length).toBeGreaterThan(0) + + const secondFile = new File(['second'], 'second.png', { type: 'image/png' }) + form.setFieldValue('avatar', secondFile) + + const secondValue = form.state.values.avatar as { file: File; uuid: string } + + expect(secondValue.file.name).toBe('second.png') + expect(typeof secondValue.uuid).toBe('string') + expect(secondValue.uuid.length).toBeGreaterThan(0) + expect(secondValue.uuid).not.toBe(firstValue.uuid) +}) + describe('form api event client', () => { it('should have debug disabled', () => { const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})