@tanstack/solid-form 0.13.7 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { FormApi, functionalUpdate } from '@tanstack/form-core'
2
- import { createComputed, onMount } from 'solid-js'
2
+ import { type JSXElement, createComputed, onMount } from 'solid-js'
3
3
  import { useStore } from '@tanstack/solid-store'
4
4
  import {
5
5
  type CreateField,
@@ -7,8 +7,6 @@ import {
7
7
  type FieldComponent,
8
8
  createField,
9
9
  } from './createField'
10
- import { formContext } from './formContext'
11
- import type { JSXElement } from 'solid-js'
12
10
  import type { FormOptions, FormState, Validator } from '@tanstack/form-core'
13
11
 
14
12
  type NoInfer<T> = [T][T extends any ? 0 : never]
@@ -16,9 +14,8 @@ type NoInfer<T> = [T][T extends any ? 0 : never]
16
14
  declare module '@tanstack/form-core' {
17
15
  // eslint-disable-next-line no-shadow
18
16
  interface FormApi<TFormData, TFormValidator> {
19
- Provider: (props: { children: any }) => JSXElement
20
17
  Field: FieldComponent<TFormData, TFormValidator>
21
- createField: CreateField<TFormData>
18
+ createField: CreateField<TFormData, TFormValidator>
22
19
  useStore: <TSelected = NoInfer<FormState<TFormData>>>(
23
20
  selector?: (state: NoInfer<FormState<TFormData>>) => TSelected,
24
21
  ) => () => TSelected
@@ -40,18 +37,17 @@ export function createForm<
40
37
  const options = opts?.()
41
38
  const formApi = new FormApi<TParentData, TFormValidator>(options)
42
39
 
43
- formApi.Provider = function Provider(props) {
44
- onMount(formApi.mount)
45
- return (
46
- <formContext.Provider {...props} value={{ formApi: formApi as never }} />
47
- )
48
- }
49
- formApi.Field = Field as any
50
- formApi.createField = createField as CreateField<TParentData>
40
+ formApi.Field = (props) => <Field {...props} form={formApi} />
41
+ formApi.createField = (props) =>
42
+ createField(() => {
43
+ return { ...props(), form: formApi }
44
+ })
51
45
  formApi.useStore = (selector) => useStore(formApi.store, selector)
52
46
  formApi.Subscribe = (props) =>
53
47
  functionalUpdate(props.children, useStore(formApi.store, props.selector))
54
48
 
49
+ onMount(formApi.mount)
50
+
55
51
  /**
56
52
  * formApi.update should not have any side effects. Think of it like a `useRef`
57
53
  * that we need to keep updated every render with the most up-to-date information.
@@ -15,8 +15,8 @@ export type FormFactory<
15
15
  createForm: (
16
16
  opts?: () => FormOptions<TFormData, TFormValidator>,
17
17
  ) => FormApi<TFormData, TFormValidator>
18
- createField: CreateField<TFormData>
19
- Field: FieldComponent<TFormData, TFormValidator>
18
+ createField: typeof createField
19
+ Field: typeof Field
20
20
  }
21
21
 
22
22
  export function createFormFactory<
@@ -31,6 +31,6 @@ export function createFormFactory<
31
31
  mergeProps(defaultOpts?.() ?? {}, opts?.() ?? {}),
32
32
  ),
33
33
  createField,
34
- Field: Field as never,
34
+ Field: Field,
35
35
  }
36
36
  }
@@ -1,5 +1,4 @@
1
- /// <reference lib="dom" />
2
- import { assertType } from 'vitest'
1
+ import { assertType, it } from 'vitest'
3
2
  import { createForm } from '../createForm'
4
3
 
5
4
  it('should type state.value properly', () => {
@@ -15,7 +14,7 @@ it('should type state.value properly', () => {
15
14
  )
16
15
 
17
16
  return (
18
- <form.Provider>
17
+ <>
19
18
  <form.Field
20
19
  name="firstName"
21
20
  children={(field) => {
@@ -30,7 +29,7 @@ it('should type state.value properly', () => {
30
29
  return null
31
30
  }}
32
31
  />
33
- </form.Provider>
32
+ </>
34
33
  )
35
34
  }
36
35
  })
@@ -48,7 +47,7 @@ it('should type onChange properly', () => {
48
47
  )
49
48
 
50
49
  return (
51
- <form.Provider>
50
+ <>
52
51
  <form.Field
53
52
  name="firstName"
54
53
  validators={{
@@ -69,7 +68,7 @@ it('should type onChange properly', () => {
69
68
  }}
70
69
  children={() => null}
71
70
  />
72
- </form.Provider>
71
+ </>
73
72
  )
74
73
  }
75
74
  })
@@ -1,8 +1,9 @@
1
- /// <reference lib="dom" />
1
+ import { describe, expect, it, vi } from 'vitest'
2
2
  import { render, waitFor } from '@solidjs/testing-library'
3
3
  import userEvent from '@testing-library/user-event'
4
- import '@testing-library/jest-dom'
5
- import { createFormFactory } from '../index'
4
+ import '@testing-library/jest-dom/vitest'
5
+ import { Index, Show } from 'solid-js'
6
+ import { createForm, createFormFactory } from '../index'
6
7
  import { sleep } from './utils'
7
8
 
8
9
  const user = userEvent.setup()
@@ -20,7 +21,7 @@ describe('createField', () => {
20
21
  const form = formFactory.createForm()
21
22
 
22
23
  return (
23
- <form.Provider>
24
+ <>
24
25
  <form.Field
25
26
  name="firstName"
26
27
  defaultValue="FirstName"
@@ -35,7 +36,7 @@ describe('createField', () => {
35
36
  )
36
37
  }}
37
38
  />
38
- </form.Provider>
39
+ </>
39
40
  )
40
41
  }
41
42
 
@@ -61,7 +62,7 @@ describe('createField', () => {
61
62
  }))
62
63
 
63
64
  return (
64
- <form.Provider>
65
+ <>
65
66
  <form.Field
66
67
  name="firstName"
67
68
  defaultValue="otherName"
@@ -76,7 +77,7 @@ describe('createField', () => {
76
77
  )
77
78
  }}
78
79
  />
79
- </form.Provider>
80
+ </>
80
81
  )
81
82
  }
82
83
 
@@ -98,7 +99,7 @@ describe('createField', () => {
98
99
  const form = formFactory.createForm()
99
100
 
100
101
  return (
101
- <form.Provider>
102
+ <>
102
103
  <form.Field
103
104
  name="firstName"
104
105
  validators={{
@@ -118,7 +119,7 @@ describe('createField', () => {
118
119
  </div>
119
120
  )}
120
121
  />
121
- </form.Provider>
122
+ </>
122
123
  )
123
124
  }
124
125
 
@@ -141,7 +142,7 @@ describe('createField', () => {
141
142
  const form = formFactory.createForm()
142
143
 
143
144
  return (
144
- <form.Provider>
145
+ <>
145
146
  <form.Field
146
147
  name="firstName"
147
148
  defaultMeta={{ isTouched: true }}
@@ -164,7 +165,7 @@ describe('createField', () => {
164
165
  )
165
166
  }}
166
167
  />
167
- </form.Provider>
168
+ </>
168
169
  )
169
170
  }
170
171
 
@@ -189,7 +190,7 @@ describe('createField', () => {
189
190
  const form = formFactory.createForm()
190
191
 
191
192
  return (
192
- <form.Provider>
193
+ <>
193
194
  <form.Field
194
195
  name="firstName"
195
196
  defaultMeta={{ isTouched: true }}
@@ -213,7 +214,7 @@ describe('createField', () => {
213
214
  </div>
214
215
  )}
215
216
  />
216
- </form.Provider>
217
+ </>
217
218
  )
218
219
  }
219
220
 
@@ -240,7 +241,7 @@ describe('createField', () => {
240
241
  const form = formFactory.createForm()
241
242
 
242
243
  return (
243
- <form.Provider>
244
+ <>
244
245
  <form.Field
245
246
  name="firstName"
246
247
  defaultMeta={{ isTouched: true }}
@@ -263,7 +264,7 @@ describe('createField', () => {
263
264
  </div>
264
265
  )}
265
266
  />
266
- </form.Provider>
267
+ </>
267
268
  )
268
269
  }
269
270
 
@@ -289,7 +290,7 @@ describe('createField', () => {
289
290
  const form = formFactory.createForm()
290
291
 
291
292
  return (
292
- <form.Provider>
293
+ <>
293
294
  <form.Field
294
295
  name="firstName"
295
296
  defaultMeta={{ isTouched: true }}
@@ -317,7 +318,7 @@ describe('createField', () => {
317
318
  </div>
318
319
  )}
319
320
  />
320
- </form.Provider>
321
+ </>
321
322
  )
322
323
  }
323
324
 
@@ -347,7 +348,7 @@ describe('createField', () => {
347
348
  const form = formFactory.createForm()
348
349
 
349
350
  return (
350
- <form.Provider>
351
+ <>
351
352
  <form.Field
352
353
  name="firstName"
353
354
  defaultMeta={{ isTouched: true }}
@@ -372,7 +373,7 @@ describe('createField', () => {
372
373
  </div>
373
374
  )}
374
375
  />
375
- </form.Provider>
376
+ </>
376
377
  )
377
378
  }
378
379
 
@@ -384,4 +385,84 @@ describe('createField', () => {
384
385
  await waitFor(() => getByText(error))
385
386
  expect(getByText(error)).toBeInTheDocument()
386
387
  })
388
+
389
+ it('should handle arrays with subvalues', async () => {
390
+ const fn = vi.fn()
391
+
392
+ function Comp() {
393
+ const form = createForm(() => ({
394
+ defaultValues: {
395
+ people: [] as Array<{ age: number; name: string }>,
396
+ },
397
+ onSubmit: ({ value }) => fn(value),
398
+ }))
399
+
400
+ return (
401
+ <div>
402
+ <form
403
+ onSubmit={(e) => {
404
+ e.preventDefault()
405
+ e.stopPropagation()
406
+ void form.handleSubmit()
407
+ }}
408
+ >
409
+ <form.Field name="people">
410
+ {(field) => (
411
+ <div>
412
+ <Show when={field().state.value.length > 0}>
413
+ {/* Do not change this to For or the test will fail */}
414
+ <Index each={field().state.value}>
415
+ {(_, i) => {
416
+ return (
417
+ <form.Field name={`people[${i}].name`}>
418
+ {(subField) => (
419
+ <div>
420
+ <label>
421
+ <div>Name for person {i}</div>
422
+ <input
423
+ value={subField().state.value}
424
+ onInput={(e) => {
425
+ subField().handleChange(
426
+ e.currentTarget.value,
427
+ )
428
+ }}
429
+ />
430
+ </label>
431
+ </div>
432
+ )}
433
+ </form.Field>
434
+ )
435
+ }}
436
+ </Index>
437
+ </Show>
438
+
439
+ <button
440
+ onClick={() => field().pushValue({ name: '', age: 0 })}
441
+ type="button"
442
+ >
443
+ Add person
444
+ </button>
445
+ </div>
446
+ )}
447
+ </form.Field>
448
+ <button type="submit">Submit</button>
449
+ </form>
450
+ </div>
451
+ )
452
+ }
453
+
454
+ const { getByText, findByLabelText, queryByText, findByText } = render(
455
+ () => <Comp />,
456
+ )
457
+
458
+ expect(queryByText('Name for person 0')).not.toBeInTheDocument()
459
+ await user.click(getByText('Add person'))
460
+ const input = await findByLabelText('Name for person 0')
461
+ expect(input).toBeInTheDocument()
462
+ await user.type(input, 'John')
463
+ await user.click(await findByText('Submit'))
464
+ expect(fn).toHaveBeenCalledWith({
465
+ people: [{ name: 'John', age: 0 }],
466
+ })
467
+ })
387
468
  })
@@ -1,6 +1,5 @@
1
- /// <reference lib="dom" />
1
+ import { describe, expect, it, vi } from 'vitest'
2
2
  import { render, screen, waitFor } from '@solidjs/testing-library'
3
- import '@testing-library/jest-dom'
4
3
  import userEvent from '@testing-library/user-event'
5
4
  import { Show, createSignal, onCleanup } from 'solid-js'
6
5
  import { createForm, createFormFactory } from '../index'
@@ -21,7 +20,7 @@ describe('createForm', () => {
21
20
  function Comp() {
22
21
  const form = formFactory.createForm()
23
22
  return (
24
- <form.Provider>
23
+ <>
25
24
  <form.Field
26
25
  name="firstName"
27
26
  defaultValue={''}
@@ -34,7 +33,7 @@ describe('createForm', () => {
34
33
  />
35
34
  )}
36
35
  />
37
- </form.Provider>
36
+ </>
38
37
  )
39
38
  }
40
39
 
@@ -62,14 +61,14 @@ describe('createForm', () => {
62
61
  }))
63
62
 
64
63
  return (
65
- <form.Provider>
64
+ <>
66
65
  <form.Field
67
66
  name="firstName"
68
67
  children={(field) => {
69
68
  return <p>{field().state.value}</p>
70
69
  }}
71
70
  />
72
- </form.Provider>
71
+ </>
73
72
  )
74
73
  }
75
74
 
@@ -91,7 +90,7 @@ describe('createForm', () => {
91
90
  }))
92
91
 
93
92
  return (
94
- <form.Provider>
93
+ <>
95
94
  <form.Field
96
95
  name="firstName"
97
96
  children={(field) => {
@@ -106,7 +105,7 @@ describe('createForm', () => {
106
105
  }}
107
106
  />
108
107
  <button onClick={form.handleSubmit}>Submit</button>
109
- </form.Provider>
108
+ </>
110
109
  )
111
110
  }
112
111
 
@@ -141,9 +140,9 @@ describe('createForm', () => {
141
140
  <button onClick={() => setMountForm(true)}>Mount form</button>
142
141
  }
143
142
  >
144
- <form.Provider>
143
+ <>
145
144
  <h1>Form mounted</h1>
146
- </form.Provider>
145
+ </>
147
146
  </Show>
148
147
  )
149
148
  }
@@ -173,7 +172,7 @@ describe('createForm', () => {
173
172
  const errors = form.useStore((s) => s.errors)
174
173
 
175
174
  return (
176
- <form.Provider>
175
+ <>
177
176
  <form.Field
178
177
  name="firstName"
179
178
  children={(field) => (
@@ -189,7 +188,7 @@ describe('createForm', () => {
189
188
  </div>
190
189
  )}
191
190
  />
192
- </form.Provider>
191
+ </>
193
192
  )
194
193
  }
195
194
 
@@ -220,7 +219,7 @@ describe('createForm', () => {
220
219
  onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap)))
221
220
 
222
221
  return (
223
- <form.Provider>
222
+ <>
224
223
  <form.Field
225
224
  name="firstName"
226
225
  defaultMeta={{ isTouched: true }}
@@ -239,7 +238,7 @@ describe('createForm', () => {
239
238
  )
240
239
  }}
241
240
  />
242
- </form.Provider>
241
+ </>
243
242
  )
244
243
  }
245
244
 
@@ -274,7 +273,7 @@ describe('createForm', () => {
274
273
  onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap)))
275
274
 
276
275
  return (
277
- <form.Provider>
276
+ <>
278
277
  <form.Field
279
278
  name="firstName"
280
279
  defaultMeta={{ isTouched: true }}
@@ -292,7 +291,7 @@ describe('createForm', () => {
292
291
  </div>
293
292
  )}
294
293
  />
295
- </form.Provider>
294
+ </>
296
295
  )
297
296
  }
298
297
 
@@ -329,7 +328,7 @@ describe('createForm', () => {
329
328
  onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap)))
330
329
 
331
330
  return (
332
- <form.Provider>
331
+ <>
333
332
  <form.Field
334
333
  name="firstName"
335
334
  defaultMeta={{ isTouched: true }}
@@ -346,7 +345,7 @@ describe('createForm', () => {
346
345
  </div>
347
346
  )}
348
347
  />
349
- </form.Provider>
348
+ </>
350
349
  )
351
350
  }
352
351
 
@@ -386,7 +385,7 @@ describe('createForm', () => {
386
385
  onCleanup(form.store.subscribe(() => setErrors(form.state.errorMap)))
387
386
 
388
387
  return (
389
- <form.Provider>
388
+ <>
390
389
  <form.Field
391
390
  name="firstName"
392
391
  defaultMeta={{ isTouched: true }}
@@ -404,7 +403,7 @@ describe('createForm', () => {
404
403
  </div>
405
404
  )}
406
405
  />
407
- </form.Provider>
406
+ </>
408
407
  )
409
408
  }
410
409
 
@@ -450,7 +449,7 @@ describe('createForm', () => {
450
449
  )
451
450
 
452
451
  return (
453
- <form.Provider>
452
+ <>
454
453
  <form.Field
455
454
  name="firstName"
456
455
  defaultMeta={{ isTouched: true }}
@@ -467,7 +466,7 @@ describe('createForm', () => {
467
466
  </div>
468
467
  )}
469
468
  />
470
- </form.Provider>
469
+ </>
471
470
  )
472
471
  }
473
472
 
@@ -1,6 +1,5 @@
1
- /// <reference lib="dom" />
1
+ import { describe, expect, it } from 'vitest'
2
2
  import { render } from '@solidjs/testing-library'
3
- import '@testing-library/jest-dom'
4
3
  import { createFormFactory } from '../index'
5
4
 
6
5
  describe('createFormFactory', () => {
@@ -21,14 +20,14 @@ describe('createFormFactory', () => {
21
20
  const form = formFactory.createForm()
22
21
 
23
22
  return (
24
- <form.Provider>
23
+ <>
25
24
  <form.Field
26
25
  name="firstName"
27
26
  children={(field) => {
28
27
  return <p>{field().state.value}</p>
29
28
  }}
30
29
  />
31
- </form.Provider>
30
+ </>
32
31
  )
33
32
  }
34
33
 
package/src/types.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  DeepKeys,
3
3
  DeepValue,
4
- FieldOptions,
4
+ FieldApiOptions,
5
5
  Validator,
6
6
  } from '@tanstack/form-core'
7
7
 
@@ -15,6 +15,12 @@ export type CreateFieldOptions<
15
15
  | Validator<TParentData, unknown>
16
16
  | undefined = undefined,
17
17
  TData extends DeepValue<TParentData, TName> = DeepValue<TParentData, TName>,
18
- > = FieldOptions<TParentData, TName, TFieldValidator, TFormValidator, TData> & {
18
+ > = FieldApiOptions<
19
+ TParentData,
20
+ TName,
21
+ TFieldValidator,
22
+ TFormValidator,
23
+ TData
24
+ > & {
19
25
  mode?: 'value' | 'array'
20
26
  }
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const solidJs = require("solid-js");
4
- const formContext = solidJs.createContext(void 0);
5
- function useFormContext() {
6
- const formApi = solidJs.useContext(formContext);
7
- if (!formApi)
8
- throw new Error(`You are trying to use the form API outside of a form!`);
9
- return formApi;
10
- }
11
- exports.formContext = formContext;
12
- exports.useFormContext = useFormContext;
13
- //# sourceMappingURL=formContext.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"formContext.cjs","sources":["../../src/formContext.ts"],"sourcesContent":["import { createContext, useContext } from 'solid-js'\nimport type { FormApi, Validator } from '@tanstack/form-core'\n\ntype FormContextType =\n | undefined\n | {\n formApi: FormApi<any, Validator<any, unknown> | undefined>\n parentFieldName?: string\n }\n\nexport const formContext = createContext<FormContextType>(undefined)\n\nexport function useFormContext() {\n const formApi: FormContextType = useContext(formContext)\n\n if (!formApi)\n throw new Error(`You are trying to use the form API outside of a form!`)\n\n return formApi\n}\n"],"names":["createContext","useContext"],"mappings":";;;AAUa,MAAA,cAAcA,QAAAA,cAA+B,MAAS;AAE5D,SAAS,iBAAiB;AACzB,QAAA,UAA2BC,mBAAW,WAAW;AAEvD,MAAI,CAAC;AACG,UAAA,IAAI,MAAM,uDAAuD;AAElE,SAAA;AACT;;;"}
@@ -1,11 +0,0 @@
1
- import type { FormApi, Validator } from '@tanstack/form-core';
2
- type FormContextType = undefined | {
3
- formApi: FormApi<any, Validator<any, unknown> | undefined>;
4
- parentFieldName?: string;
5
- };
6
- export declare const formContext: import("solid-js").Context<FormContextType>;
7
- export declare function useFormContext(): {
8
- formApi: FormApi<any, Validator<any, unknown> | undefined>;
9
- parentFieldName?: string | undefined;
10
- };
11
- export {};
@@ -1,11 +0,0 @@
1
- import type { FormApi, Validator } from '@tanstack/form-core';
2
- type FormContextType = undefined | {
3
- formApi: FormApi<any, Validator<any, unknown> | undefined>;
4
- parentFieldName?: string;
5
- };
6
- export declare const formContext: import("solid-js").Context<FormContextType>;
7
- export declare function useFormContext(): {
8
- formApi: FormApi<any, Validator<any, unknown> | undefined>;
9
- parentFieldName?: string | undefined;
10
- };
11
- export {};
@@ -1,13 +0,0 @@
1
- import { createContext, useContext } from "solid-js";
2
- const formContext = createContext(void 0);
3
- function useFormContext() {
4
- const formApi = useContext(formContext);
5
- if (!formApi)
6
- throw new Error(`You are trying to use the form API outside of a form!`);
7
- return formApi;
8
- }
9
- export {
10
- formContext,
11
- useFormContext
12
- };
13
- //# sourceMappingURL=formContext.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"formContext.js","sources":["../../src/formContext.ts"],"sourcesContent":["import { createContext, useContext } from 'solid-js'\nimport type { FormApi, Validator } from '@tanstack/form-core'\n\ntype FormContextType =\n | undefined\n | {\n formApi: FormApi<any, Validator<any, unknown> | undefined>\n parentFieldName?: string\n }\n\nexport const formContext = createContext<FormContextType>(undefined)\n\nexport function useFormContext() {\n const formApi: FormContextType = useContext(formContext)\n\n if (!formApi)\n throw new Error(`You are trying to use the form API outside of a form!`)\n\n return formApi\n}\n"],"names":[],"mappings":";AAUa,MAAA,cAAc,cAA+B,MAAS;AAE5D,SAAS,iBAAiB;AACzB,QAAA,UAA2B,WAAW,WAAW;AAEvD,MAAI,CAAC;AACG,UAAA,IAAI,MAAM,uDAAuD;AAElE,SAAA;AACT;"}
@@ -1,20 +0,0 @@
1
- import { createContext, useContext } from 'solid-js'
2
- import type { FormApi, Validator } from '@tanstack/form-core'
3
-
4
- type FormContextType =
5
- | undefined
6
- | {
7
- formApi: FormApi<any, Validator<any, unknown> | undefined>
8
- parentFieldName?: string
9
- }
10
-
11
- export const formContext = createContext<FormContextType>(undefined)
12
-
13
- export function useFormContext() {
14
- const formApi: FormContextType = useContext(formContext)
15
-
16
- if (!formApi)
17
- throw new Error(`You are trying to use the form API outside of a form!`)
18
-
19
- return formApi
20
- }