Skip to content

Commit c978946

Browse files
fix(core): move to native compatible uuid (#1753)
1 parent 633db9e commit c978946

File tree

10 files changed

+104
-29
lines changed

10 files changed

+104
-29
lines changed

.changeset/cute-knives-hug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/form-core': minor
3+
---
4+
5+
Removes UUID from package.json for native environments. Reverts formId to a getter function.

packages/form-core/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@
5252
],
5353
"dependencies": {
5454
"@tanstack/devtools-event-client": "^0.3.2",
55-
"@tanstack/store": "^0.7.7",
56-
"uuid": "^13.0.0"
55+
"@tanstack/store": "^0.7.7"
5756
},
5857
"devDependencies": {
5958
"arktype": "^2.1.22",

packages/form-core/src/FormApi.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Derived, Store, batch } from '@tanstack/store'
2-
import { v4 as uuidv4 } from 'uuid'
2+
33
import {
44
deleteBy,
55
determineFormLevelErrorSourceAndValue,
@@ -12,6 +12,7 @@ import {
1212
isNonEmptyArray,
1313
mergeOpts,
1414
setBy,
15+
uuid,
1516
} from './utils'
1617
import { defaultValidationLogic } from './ValidationLogic'
1718

@@ -1000,7 +1001,7 @@ export class FormApi<
10001001
formListeners: {} as Record<ListenerCause, never>,
10011002
}
10021003

1003-
this._formId = opts?.formId ?? uuidv4()
1004+
this._formId = opts?.formId ?? uuid()
10041005

10051006
this._devtoolsSubmissionOverride = false
10061007

@@ -1329,7 +1330,7 @@ export class FormApi<
13291330
})
13301331
}
13311332

1332-
formId(): string | undefined {
1333+
get formId(): string {
13331334
return this._formId
13341335
}
13351336

packages/form-core/src/utils.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,3 +558,45 @@ export function mergeOpts<T>(
558558

559559
return { ...originalOpts, ...overrides }
560560
}
561+
562+
/*
563+
/ credit is due to https://github.com/lukeed/uuid for this code, with current npm
564+
/ attacks we didn't feel comfortable installing directly from npm. But big appreciation
565+
/ from the TanStack Form team <3.
566+
*/
567+
568+
let IDX = 256
569+
const HEX: string[] = []
570+
let BUFFER: number[] | undefined
571+
572+
while (IDX--) {
573+
HEX[IDX] = (IDX + 256).toString(16).substring(1)
574+
}
575+
576+
export function uuid(): string {
577+
let i = 0
578+
let num: number
579+
let out = ''
580+
581+
if (!BUFFER || IDX + 16 > 256) {
582+
BUFFER = new Array<number>(256)
583+
i = 256
584+
while (i--) {
585+
BUFFER[i] = (256 * Math.random()) | 0
586+
}
587+
i = 0
588+
IDX = 0
589+
}
590+
591+
for (; i < 16; i++) {
592+
num = BUFFER[IDX + i] as number
593+
if (i === 6) out += HEX[(num & 15) | 64]
594+
else if (i === 8) out += HEX[(num & 63) | 128]
595+
else out += HEX[num]
596+
597+
if (i & 1 && i > 1 && i < 11) out += '-'
598+
}
599+
600+
IDX++
601+
return out
602+
}

packages/form-core/tests/FormApi.spec.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3952,5 +3952,14 @@ it('should accept formId and return it', () => {
39523952
})
39533953
form.mount()
39543954

3955-
expect(form.formId()).toEqual('age')
3955+
expect(form.formId).toEqual('age')
3956+
})
3957+
3958+
it('should generate a formId if not provided', () => {
3959+
const form = new FormApi({
3960+
defaultValues: { age: 0 },
3961+
})
3962+
form.mount()
3963+
3964+
expect(form.formId.length).toBeGreaterThan(1)
39563965
})

packages/form-core/tests/formOptions.test-d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,8 @@ describe('formOptions', () => {
276276
} as FormData,
277277
validators: {
278278
onSubmit: ({ formApi }) => {
279-
if (formApi.formId() === undefined) {
280-
return 'needs formId'
279+
if (formApi.formId) {
280+
return 'I just need an error'
281281
}
282282
return undefined
283283
},
@@ -287,7 +287,7 @@ describe('formOptions', () => {
287287
const form = new FormApi(formOpts)
288288

289289
expectTypeOf(form.state.errors).toEqualTypeOf<
290-
('needs formId' | undefined)[]
290+
('I just need an error' | undefined)[]
291291
>()
292292

293293
const form2 = new FormApi({
@@ -320,7 +320,7 @@ describe('formOptions', () => {
320320
})
321321

322322
expectTypeOf(form3.state.errors).toEqualTypeOf<
323-
(undefined | 'Too short!' | 'needs formId')[]
323+
(undefined | 'Too short!' | 'I just need an error')[]
324324
>()
325325
})
326326
})

packages/form-core/tests/utils.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
makePathArray,
1111
mergeOpts,
1212
setBy,
13+
uuid,
1314
} from '../src/index'
1415

1516
describe('getBy', () => {
@@ -769,3 +770,34 @@ describe('mergeOpts', () => {
769770
expect(mergeOpts(original, {})).toEqual({ foo: 'test' })
770771
})
771772
})
773+
774+
describe('uuid', () => {
775+
it('should return a string', () => {
776+
const id = uuid()
777+
expect(typeof id).toBe('string')
778+
})
779+
780+
it('should match UUID v4 format', () => {
781+
const id = uuid()
782+
const uuidV4Regex =
783+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
784+
expect(id).toMatch(uuidV4Regex)
785+
})
786+
787+
it('should generate different values on multiple calls', () => {
788+
const ids = new Set(Array.from({ length: 100 }, () => uuid()))
789+
expect(ids.size).toBe(100)
790+
})
791+
792+
it('should always produce 36 characters', () => {
793+
const id = uuid()
794+
expect(id.length).toBe(36)
795+
})
796+
797+
it('should set correct version (4) and variant bits', () => {
798+
const id = uuid()
799+
const parts = id.split('-')
800+
expect(parts[2]?.[0]).toBe('4')
801+
expect(['8', '9', 'a', 'b']).toContain(parts[3]?.[0])
802+
})
803+
})

packages/react-form/tests/createFormHook.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,8 +540,8 @@ describe('createFormHook', () => {
540540
const form = useFormContext()
541541

542542
return (
543-
<button type="submit" form={form.formId()} data-testid="formId-target">
544-
{form.formId()}
543+
<button type="submit" form={form.formId} data-testid="formId-target">
544+
{form.formId}
545545
</button>
546546
)
547547
}
@@ -554,7 +554,7 @@ describe('createFormHook', () => {
554554
return (
555555
<form.AppForm>
556556
<form
557-
id={form.formId()}
557+
id={form.formId}
558558
onSubmit={(e) => {
559559
e.preventDefault()
560560
form.handleSubmit()

packages/react-form/tests/useForm.test.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ describe('useForm', () => {
864864
return (
865865
<>
866866
<form
867-
id={form.formId()}
867+
id={form.formId}
868868
onSubmit={(e) => {
869869
e.preventDefault()
870870
form.handleSubmit()
@@ -878,12 +878,8 @@ describe('useForm', () => {
878878
)}
879879
/>
880880

881-
<button
882-
type="submit"
883-
form={form.formId()}
884-
data-testid="formId-target"
885-
>
886-
{form.formId()}
881+
<button type="submit" form={form.formId} data-testid="formId-target">
882+
{form.formId}
887883
</button>
888884
</>
889885
)

pnpm-lock.yaml

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)