@@ -37,26 +37,39 @@ export interface IValidation<Values> {
3737 isValid ( ) : boolean
3838}
3939
40+ interface CachedInputProps < Value > {
41+ value : Value | null
42+ message : string | null
43+ required : boolean
44+ props : ValidationProps < Value >
45+ }
46+
4047class Validation < Values > implements IValidation < Values > {
4148
4249 private readonly changeValue : ( key : string , value : any ) => void
4350 private readonly currentValues : Values
4451 private readonly currentValidations ?: Validations < Values >
4552 private readonly shouldValidate : Map < keyof Values , boolean >
4653 private readonly cachedMessages : Map < keyof Values , string | null >
54+ private readonly cachedSetters : Map < keyof Values , ( value : any ) => void >
55+ private readonly cachedProps : Map < keyof Values , CachedInputProps < any > >
4756
4857 constructor (
4958 changeValue : ( key : string , value : any ) => void ,
5059 values : Values ,
5160 validations : Validations < Values > ,
5261 shouldValidate : Map < keyof Values , boolean > = new Map < keyof Values , boolean > ( ) ,
53- cachedMessages : Map < keyof Values , string | null > = new Map ( )
62+ cachedMessages : Map < keyof Values , string | null > = new Map ( ) ,
63+ cachedSetters : Map < keyof Values , ( value : any ) => void > = new Map ( ) ,
64+ cachedProps : Map < keyof Values , CachedInputProps < any > > = new Map ( )
5465 ) {
5566 this . changeValue = changeValue
5667 this . currentValues = values
5768 this . currentValidations = validations
5869 this . shouldValidate = shouldValidate
5970 this . cachedMessages = cachedMessages
71+ this . cachedSetters = cachedSetters
72+ this . cachedProps = cachedProps
6073 }
6174
6275 isValid ( ) : boolean {
@@ -94,23 +107,43 @@ class Validation<Values> implements IValidation<Values> {
94107 message = this . cachedMessages . get ( key ) ?? null
95108 }
96109
97- return {
110+ const required = Boolean ( this . currentValidations && this . currentValidations [ key ] )
111+
112+ // Reuse the cached props object as long as nothing for this key changed, so
113+ // consumers can memoize directly on the getInputProps output.
114+ const cached = this . cachedProps . get ( key )
115+ if ( cached && Object . is ( cached . value , currentValue ) && cached . message === message && cached . required === required ) {
116+ return cached . props
117+ }
118+
119+ // One setValue per key for the lifetime of the form: changeValue reads from
120+ // valuesRef and never goes stale, so the wrapper never has to be rebuilt.
121+ let setValue = this . cachedSetters . get ( key )
122+ if ( ! setValue ) {
123+ const changeValue = this . changeValue
124+ setValue = ( value : any ) => {
125+ changeValue ( currentName , value )
126+ }
127+ this . cachedSetters . set ( key , setValue )
128+ }
129+
130+ const props : ValidationProps < Values [ Key ] > = {
98131 // @ts -ignore – z.B. wenn dein Input `defaultValue` kennt
99132 defaultValue : currentValue ?? undefined ,
100133 initialValue : currentValue ?? undefined ,
101134 formValidation : {
102- setValue : ( value : any ) => {
103- this . changeValue ( currentName , value )
104- } ,
135+ setValue,
105136 ...( {
106137 notValidMessage : message ,
107138 valid : message === null ,
108139 } )
109140 } ,
110- ...( this . currentValidations && this . currentValidations [ key ]
141+ ...( required
111142 ? { required : true }
112143 : { } )
113144 }
145+ this . cachedProps . set ( key , { value : currentValue , message, required, props} )
146+ return props
114147 }
115148}
116149
@@ -129,6 +162,8 @@ export const useForm = <
129162 const initValues = React . useMemo ( ( ) => initialValues as Values , [ initialValues ] )
130163 const [ values , setValues ] = useState < Values > ( initValues )
131164 const cachedMessagesRef = useRef < Map < keyof Values , string | null > > ( new Map ( ) )
165+ const cachedSettersRef = useRef < Map < keyof Values , ( value : any ) => void > > ( new Map ( ) )
166+ const cachedPropsRef = useRef < Map < keyof Values , CachedInputProps < any > > > ( new Map ( ) )
132167 const valuesRef = useRef < Values > ( values )
133168 valuesRef . current = values
134169
@@ -142,18 +177,30 @@ export const useForm = <
142177 values ,
143178 validate ,
144179 useInitialValidation ? new Map < keyof Values , boolean > ( Object . keys ( initValues ) . map ( k => [ k as keyof Values , true ] ) ) : new Map < keyof Values , boolean > ( ) ,
145- cachedMessagesRef . current
180+ cachedMessagesRef . current ,
181+ cachedSettersRef . current ,
182+ cachedPropsRef . current
146183 ) )
147184
185+ const didInitRef = useRef ( false )
148186 useEffect ( ( ) => {
149187 valuesRef . current = initValues
150188 setValues ( initValues )
189+ // Form reset: props must be rebuilt from the new initial values. The setter
190+ // cache survives on purpose — changeValue is stable, so the setters stay valid.
191+ // On mount the cache only holds props built from these initValues, so keep it.
192+ if ( didInitRef . current ) {
193+ cachedPropsRef . current . clear ( )
194+ }
195+ didInitRef . current = true
151196 setValidation ( new Validation < Values > (
152197 changeValue ,
153198 initValues ,
154199 validate ,
155200 useInitialValidation ? new Map < keyof Values , boolean > ( Object . keys ( initValues ) . map ( k => [ k as keyof Values , true ] ) ) : new Map < keyof Values , boolean > ( ) ,
156- cachedMessagesRef . current
201+ cachedMessagesRef . current ,
202+ cachedSettersRef . current ,
203+ cachedPropsRef . current
157204 ) )
158205 } , [ initValues ] )
159206
@@ -170,7 +217,9 @@ export const useForm = <
170217 currentValues ,
171218 validate ,
172219 shouldValidateMap ,
173- cachedMessagesRef . current
220+ cachedMessagesRef . current ,
221+ cachedSettersRef . current ,
222+ cachedPropsRef . current
174223 )
175224
176225 setValidation ( currentValidation )
0 commit comments