Skip to content
Open
28 changes: 27 additions & 1 deletion packages/form-core/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -52,7 +66,10 @@ export function setBy(obj: any, _path: any, updater: Updater<any>) {

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()
Expand Down Expand Up @@ -422,6 +439,15 @@ export const isGlobalFormValidationError = (
}

export function evaluate<T>(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
}
Expand Down
33 changes: 32 additions & 1 deletion packages/form-core/tests/FormApi.spec.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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(() => {})
Expand Down