Skip to content

Commit 4b80589

Browse files
committed
chore: refactor to remove setTimeout
1 parent 802eb79 commit 4b80589

File tree

2 files changed

+53
-12
lines changed

2 files changed

+53
-12
lines changed

packages/react-form/src/useForm.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { FormApi, functionalUpdate, mergeAndUpdate } from '@tanstack/form-core'
44
import { useStore } from '@tanstack/react-store'
5-
import { useMemo, useState } from 'react'
5+
import { useMemo, useRef, useState } from 'react'
66
import { Field } from './useField'
77
import { useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'
88
import { useFormId } from './useFormId'
@@ -211,8 +211,6 @@ export function useForm<
211211
setPrevFormId(formId)
212212
}
213213

214-
const transform = useMemo(() => opts?.transform, [opts?.transform])
215-
216214
const extendedFormApi = useMemo(() => {
217215
const extendedApi: ReactFormExtendedApi<
218216
TFormData,
@@ -241,13 +239,6 @@ export function useForm<
241239
},
242240
} as never
243241

244-
if (transform) {
245-
// Cannot call synchronously, as otherwise we're setting state mid-render, which breaks React
246-
setTimeout(() => {
247-
mergeAndUpdate(extendedApi, transform as never)
248-
}, 0)
249-
}
250-
251242
extendedApi.Field = function APIField(props) {
252243
return <Field {...props} form={formApi} />
253244
}
@@ -263,7 +254,7 @@ export function useForm<
263254
}
264255

265256
return extendedApi
266-
}, [formApi, transform])
257+
}, [formApi])
267258

268259
useIsomorphicLayoutEffect(formApi.mount, [])
269260

@@ -275,5 +266,17 @@ export function useForm<
275266
formApi.update(opts)
276267
})
277268

269+
const hasRan = useRef(false)
270+
271+
useIsomorphicLayoutEffect(() => {
272+
if (!hasRan.current) return
273+
if (!opts?.transform) return
274+
mergeAndUpdate(formApi, opts.transform as never)
275+
}, [formApi, opts?.transform])
276+
277+
useIsomorphicLayoutEffect(() => {
278+
hasRan.current = true
279+
})
280+
278281
return extendedFormApi
279282
}

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

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'
22
import { render, waitFor } from '@testing-library/react'
33
import { userEvent } from '@testing-library/user-event'
44
import { useStore } from '@tanstack/react-store'
5-
import { useCallback, useEffect, useState } from 'react'
5+
import { useCallback, useEffect, useReducer, useState } from 'react'
66
import { mergeForm, useForm } from '../src/index'
77
import { sleep } from './utils'
88

@@ -1037,4 +1037,42 @@ describe('useForm', () => {
10371037
const { getByText } = render(<Comp />)
10381038
getByText('What 1')
10391039
})
1040+
1041+
it('should handle stable transforms to update the base form on subsequent renders', async () => {
1042+
function Comp() {
1043+
const [renders, setRenders] = useState(0)
1044+
const form = useForm({
1045+
defaultValues: {
1046+
test: 'Hello',
1047+
},
1048+
transform: useCallback(
1049+
(baseForm: unknown) => {
1050+
return mergeForm(baseForm as never, {
1051+
values: {
1052+
test: renders === 0 ? 'First' : 'Another',
1053+
},
1054+
})
1055+
},
1056+
[renders],
1057+
),
1058+
})
1059+
1060+
return (
1061+
<div>
1062+
<form.Field
1063+
name="test"
1064+
children={(field) => <p>{field.state.value}</p>}
1065+
/>
1066+
<button onClick={() => setRenders((v) => v + 1)} type={'button'}>
1067+
Rerender
1068+
</button>
1069+
</div>
1070+
)
1071+
}
1072+
1073+
const { findByText, getByText } = render(<Comp />)
1074+
await findByText('First')
1075+
await user.click(getByText('Rerender'))
1076+
await findByText('Another')
1077+
})
10401078
})

0 commit comments

Comments
 (0)