Skip to content
Merged
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
14 changes: 12 additions & 2 deletions packages/form-core/src/FormApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ export type FormOptions<TData, ValidatorType> = {
asyncDebounceMs?: number
validator?: ValidatorType
onMount?: ValidateOrFn<TData, ValidatorType>
onMountAsync?: ValidateAsyncFn<TData, ValidatorType>
onMountAsyncDebounceMs?: number
onChange?: ValidateOrFn<TData, ValidatorType>
onChangeAsync?: ValidateAsyncFn<TData, ValidatorType>
onChangeAsyncDebounceMs?: number
Expand Down Expand Up @@ -170,6 +168,18 @@ export class FormApi<TFormData, ValidatorType> {
this.update(opts || {})
}

mount = () => {
if (typeof this.options.onMount === 'function') {
return this.options.onMount(this.state.values, this)
}
if (this.options.validator) {
return (this.options.validator as Validator<TFormData>)().validate(
this.state.values,
this.options.onMount,
)
}
}

update = (options?: FormOptions<TFormData, ValidatorType>) => {
if (!options) return

Expand Down
30 changes: 16 additions & 14 deletions packages/form-core/src/tests/FormApi.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { FieldApi } from '../FieldApi'
describe('form api', () => {
it('should get default form state', () => {
const form = new FormApi()

form.mount()
expect(form.state).toEqual({
values: {},
fieldMeta: {},
Expand All @@ -31,7 +31,7 @@ describe('form api', () => {
name: 'test',
},
})

form.mount()
expect(form.state).toEqual({
values: {
name: 'test',
Expand All @@ -58,7 +58,7 @@ describe('form api', () => {
submissionAttempts: 30,
},
})

form.mount()
expect(form.state).toEqual({
values: {},
fieldMeta: {},
Expand All @@ -83,7 +83,7 @@ describe('form api', () => {
name: 'test',
},
})

form.mount()
form.update({
defaultValues: {
name: 'other',
Expand Down Expand Up @@ -119,7 +119,7 @@ describe('form api', () => {
name: 'test',
},
})

form.mount()
form.setFieldValue('name', 'other')
form.state.submissionAttempts = 300

Expand Down Expand Up @@ -151,7 +151,7 @@ describe('form api', () => {
name: 'test',
},
})

form.mount()
expect(form.getFieldValue('name')).toEqual('test')
})

Expand All @@ -161,7 +161,7 @@ describe('form api', () => {
name: 'test',
},
})

form.mount()
form.setFieldValue('name', 'other')

expect(form.getFieldValue('name')).toEqual('other')
Expand All @@ -173,7 +173,7 @@ describe('form api', () => {
names: ['test'],
},
})

form.mount()
form.pushFieldValue('names', 'other')

expect(form.getFieldValue('names')).toStrictEqual(['test', 'other'])
Expand All @@ -185,7 +185,7 @@ describe('form api', () => {
names: ['one', 'two', 'three'],
},
})

form.mount()
form.insertFieldValue('names', 1, 'other')

expect(form.getFieldValue('names')).toStrictEqual(['one', 'other', 'three'])
Expand All @@ -197,7 +197,7 @@ describe('form api', () => {
names: ['one', 'two', 'three'],
},
})

form.mount()
form.removeFieldValue('names', 1)

expect(form.getFieldValue('names')).toStrictEqual(['one', 'three'])
Expand All @@ -209,7 +209,7 @@ describe('form api', () => {
names: ['one', 'two', 'three'],
},
})

form.mount()
form.swapFieldValues('names', 1, 2)

expect(form.getFieldValue('names')).toStrictEqual(['one', 'three', 'two'])
Expand All @@ -221,7 +221,7 @@ describe('form api', () => {
name: 'test',
},
})

form.mount()
form.setFieldValue('name', 'other')

expect(form.getFieldValue('name')).toEqual('other')
Expand All @@ -237,7 +237,7 @@ describe('form api', () => {
name: 'test',
},
})

form.mount()
expect(form.getFieldValue('name')).toEqual('test')

form.update({
Expand All @@ -255,7 +255,7 @@ describe('form api', () => {
name: 'one',
},
})

form.mount()
expect(form.getFieldValue('name')).toEqual('one')

form.setFieldValue('name', 'two', { touch: true })
Expand Down Expand Up @@ -297,6 +297,8 @@ describe('form api', () => {
onChange: (v) => (v.length > 0 ? undefined : 'required'),
})

form.mount()

field.mount()

field.handleChange('one')
Expand Down
33 changes: 33 additions & 0 deletions packages/react-form/src/tests/useForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,37 @@ describe('useForm', () => {
expect(getByText('Submitted data: OtherName')).toBeInTheDocument(),
)
})

it('should run on form mount', async () => {
function Comp() {
const [formMounted, setFormMounted] = React.useState(false)
const [mountForm, setMountForm] = React.useState(false)

const form = useForm({
defaultValues: {
firstName: 'FirstName',
},
onMount: () => {
setFormMounted(true)
return undefined
},
})

return (
<>
{mountForm ? (
<form.Provider>
<h1>{formMounted ? 'Form mounted' : 'Not mounted'}</h1>
</form.Provider>
) : (
<button onClick={() => setMountForm(true)}>Mount form</button>
)}
</>
)
}

const { getByText } = render(<Comp />)
await user.click(getByText('Mount form'))
await waitFor(() => expect(getByText('Form mounted')).toBeInTheDocument())
})
})
1 change: 1 addition & 0 deletions packages/react-form/src/useForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function useForm<TData, FormValidator>(
const api = new FormApi<TData>(opts)

api.Provider = function Provider(props) {
useIsomorphicLayoutEffect(formApi.mount, [])
return <formContext.Provider {...props} value={{ formApi: api }} />
}
api.Field = Field as any
Expand Down
3 changes: 2 additions & 1 deletion packages/solid-form/src/createForm.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FormOptions, FormState } from '@tanstack/form-core'
import { FormApi, functionalUpdate } from '@tanstack/form-core'
import { createComputed, type JSXElement } from 'solid-js'
import { createComputed, onMount, type JSXElement } from 'solid-js'
import { useStore } from '@tanstack/solid-store'
import {
Field,
Expand Down Expand Up @@ -35,6 +35,7 @@ export function createForm<TData, FormValidator>(
const formApi = new FormApi<TData, FormValidator>(options)

formApi.Provider = function Provider(props) {
onMount(formApi.mount)
return <formContext.Provider {...props} value={{ formApi: formApi }} />
}
formApi.Field = Field as any
Expand Down
34 changes: 34 additions & 0 deletions packages/solid-form/src/tests/createForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { render, screen, waitFor } from '@solidjs/testing-library'
import '@testing-library/jest-dom'
import userEvent from '@testing-library/user-event'
import { createFormFactory, createForm } from '..'
import { Show, createSignal } from 'solid-js'

const user = userEvent.setup()

Expand Down Expand Up @@ -113,4 +114,37 @@ describe('createForm', () => {
await user.click(getByText('Submit'))
expect(submittedData?.firstName).toEqual('OtherName')
})

it('should run on form mount', async () => {
const [formMounted, setFormMounted] = createSignal(false)
const [mountForm, setMountForm] = createSignal(false)
function Comp() {
const form = createForm(() => ({
defaultValues: {
firstName: 'FirstName',
},
onMount: () => {
setFormMounted(true)
return undefined
},
}))

return (
<Show
when={mountForm()}
fallback={
<button onClick={() => setMountForm(true)}>Mount form</button>
}
>
<form.Provider>
<h1>Form mounted</h1>
</form.Provider>
</Show>
)
}

const { getByText } = render(() => <Comp />)
await user.click(getByText('Mount form'))
expect(formMounted()).toBe(true)
})
})
31 changes: 31 additions & 0 deletions packages/vue-form/src/tests/useForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,35 @@ describe('useForm', () => {
expect(getByText('Submitted data: OtherName')).toBeInTheDocument(),
)
})

it('should run on form mount', async () => {
const Comp = defineComponent(() => {
const formMounted = ref(false)
const mountForm = ref(false)

const form = useForm({
defaultValues: {
firstName: 'FirstName',
},
onMount: () => {
formMounted.value = true
return undefined
},
})
form.provideFormContext()

return () =>
mountForm.value ? (
<form.Provider>
<h1>{formMounted.value ? 'Form mounted' : 'Not mounted'}</h1>
</form.Provider>
) : (
<button onClick={() => (mountForm.value = true)}>Mount form</button>
)
})

const { getByText, findByText } = render(Comp)
await user.click(getByText('Mount form'))
expect(await findByText('Form mounted')).toBeInTheDocument()
})
})
2 changes: 2 additions & 0 deletions packages/vue-form/src/useForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
type SlotsType,
type SetupContext,
defineComponent,
onMounted,
} from 'vue-demi'

declare module '@tanstack/form-core' {
Expand Down Expand Up @@ -39,6 +40,7 @@ export function useForm<TData, FormValidator>(

api.Provider = defineComponent(
(_, context) => {
onMounted(formApi.mount)
provideFormContext({ formApi: formApi as never })
return () => context.slots.default!()
},
Expand Down