File tree Expand file tree Collapse file tree 3 files changed +65
-1
lines changed
Expand file tree Collapse file tree 3 files changed +65
-1
lines changed Original file line number Diff line number Diff line change @@ -494,6 +494,10 @@ export interface FieldApiOptions<
494494 TFormOnServer ,
495495 TParentSubmitMeta
496496 >
497+ /**
498+ * If true, the default value will not be set in the constructor.
499+ */
500+ deferDefaultValue ?: boolean
497501}
498502
499503export type FieldMetaBase <
@@ -996,7 +1000,7 @@ export class FieldApi<
9961000 this . form = opts . form as never
9971001 this . name = opts . name as never
9981002 this . timeoutIds = { } as Record < ValidationCause , never >
999- if ( opts . defaultValue !== undefined ) {
1003+ if ( ! opts . deferDefaultValue && opts . defaultValue !== undefined ) {
10001004 this . form . setFieldValue ( this . name , opts . defaultValue as never , {
10011005 dontUpdateMeta : true ,
10021006 } )
Original file line number Diff line number Diff line change @@ -181,6 +181,7 @@ export function useField<
181181 ...opts ,
182182 form : opts . form ,
183183 name : opts . name ,
184+ deferDefaultValue : true , // Prevents https://react.dev/link/setstate-in-render by setting the default value after initial mount for React
184185 } )
185186
186187 const extendedApi : typeof api &
@@ -204,6 +205,18 @@ export function useField<
204205
205206 useIsomorphicLayoutEffect ( fieldApi . mount , [ fieldApi ] )
206207
208+ useIsomorphicLayoutEffect ( ( ) => {
209+ if ( fieldApi . options . defaultValue !== undefined ) {
210+ fieldApi . form . setFieldValue (
211+ fieldApi . name ,
212+ fieldApi . options . defaultValue as never ,
213+ {
214+ dontUpdateMeta : true ,
215+ } ,
216+ )
217+ }
218+ } , [ fieldApi ] )
219+
207220 /**
208221 * fieldApi.update should not have any side effects. Think of it like a `useRef`
209222 * that we need to keep updated every render with the most up-to-date information.
Original file line number Diff line number Diff line change @@ -1130,4 +1130,51 @@ describe('useField', () => {
11301130 // field2 should not have rerendered
11311131 expect ( renderCount . field2 ) . toBe ( field2InitialRender )
11321132 } )
1133+
1134+ it ( 'should handle defaultValue without setstate-in-render error' , async ( ) => {
1135+ // Spy on console.error before rendering
1136+ const consoleErrorSpy = vi . spyOn ( console , 'error' )
1137+
1138+ function Comp ( ) {
1139+ const form = useForm ( {
1140+ defaultValues : {
1141+ fieldOne : '' ,
1142+ fieldTwo : '' ,
1143+ } ,
1144+ } )
1145+
1146+ const fieldOne = useStore ( form . store , ( state ) => state . values . fieldOne )
1147+
1148+ return (
1149+ < form >
1150+ < form . Field
1151+ name = "fieldOne"
1152+ children = { ( field ) => {
1153+ return (
1154+ < input
1155+ data-testid = { field . name }
1156+ id = { field . name }
1157+ value = { field . state . value }
1158+ onChange = { ( e ) => field . handleChange ( e . target . value ) }
1159+ />
1160+ )
1161+ } }
1162+ />
1163+ { fieldOne && (
1164+ < form . Field
1165+ name = "fieldTwo"
1166+ defaultValue = "default field two value"
1167+ children = { ( _ ) => null }
1168+ />
1169+ ) }
1170+ </ form >
1171+ )
1172+ }
1173+
1174+ const { getByTestId } = render ( < Comp /> )
1175+ await user . type ( getByTestId ( 'fieldOne' ) , 'John' )
1176+
1177+ // Should not log an error
1178+ expect ( consoleErrorSpy ) . not . toHaveBeenCalled ( )
1179+ } )
11331180} )
You can’t perform that action at this time.
0 commit comments