• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iusehooks / usetheform / 14331086453

08 Apr 2025 10:20AM UTC coverage: 98.506% (-0.5%) from 98.962%
14331086453

push

github

web-flow
Feature/fix strictmode react18 (#50)

* feature/fix-strictmode-react18

* Cypress 11 + tests

Co-authored-by: antoniopangallo <a.pangallo85@gmail.com>

585 of 603 branches covered (97.01%)

Branch coverage included in aggregate %.

37 of 38 new or added lines in 4 files covered. (97.37%)

2 existing lines in 1 file now uncovered.

800 of 803 relevant lines covered (99.63%)

1479.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.67
/src/hooks/useObject.js
1
import { useRef, useEffect, useCallback, useMemo } from "react";
2
import { useOwnContext } from "./useOwnContext";
3
import { useNameProp } from "./commons/useNameProp";
4
import { useMapFields } from "./useMapFields";
5
import { useValidators } from "./useValidators";
6
import { validateProps } from "./../utils/validateProps";
7
import { updateState } from "./../utils/updateState";
8
import { chainReducers } from "./../utils/chainReducers";
9
import { useValidationFunction } from "./commons/useValidationFunction";
10
import { useValidationFunctionAsync } from "./commons/useValidationFunctionAsync";
11
import { STATUS } from "./../utils/constants";
12
import { DISPATCHER_LABEL } from "./../utils/constants";
13
import { noop } from "./../utils/noop";
14

15
const initArray = [];
18✔
16
const initObject = {};
18✔
17
const validatorsDefault = [];
18✔
18

19
export function useObject(props) {
20
  const context = useOwnContext();
4,005✔
21

22
  const {
23
    name,
24
    index,
25
    type,
26
    value: initValue,
27
    reducers,
28
    validators: validatorsFuncs = validatorsDefault,
3,929✔
29
    onValidation = noop,
3,929✔
30
    resetSyncErr = noop,
3,929✔
31
    resetAsyncErr = noop,
3,887✔
32
    asyncValidator,
33
    onAsyncValidation = noop,
3,887✔
34
    touched = false
3,955✔
35
  } = props;
4,005✔
36

37
  const {
38
    nameProp,
39
    uniqueIDarrayContext,
40
    setNameProp,
41
    unMountIndex
42
  } = useNameProp(context, name, index);
4,005✔
43

44
  if (process.env.NODE_ENV !== "production") {
4,005!
45
    validateProps(
4,005✔
46
      "<Collection /> ",
47
      { ...props, index: nameProp.current },
48
      context.type
49
    );
50
  }
51

52
  const { unRegisterField, mapFields, updateRegisteredField } = useMapFields(
3,997✔
53
    nameProp,
54
    context,
55
    type
56
  );
57

58
  const applyReducers = useMemo(() => chainReducers(reducers), []);
3,997✔
59

60
  const isMounted = useRef(false);
3,997✔
61
  const stillMounted = useCallback(() => isMounted.current, []);
3,997✔
62

63
  const isArray = type && type === "array";
3,997✔
64
  const init = initValue || (isArray ? initArray : initObject);
3,997✔
65
  const state = useRef(init);
3,997✔
66
  const memoInitialState = useRef(init);
3,997✔
67
  const prevState = useRef(isArray ? initArray : initObject);
3,997✔
68
  const valueFieldLastSyncCheck = useRef(null);
3,997✔
69
  const memoState = useRef(null);
3,997✔
70

71
  // getValue from parent context
72
  if (!isMounted.current) {
3,997✔
73
    state.current = initValue || context.state[nameProp.current] || init;
338✔
74
  } else {
75
    state.current =
3,659✔
76
      context.state[nameProp.current] || (isArray ? initArray : initObject);
4,711✔
77
  }
78

79
  const formState = useRef(null);
3,997✔
80
  formState.current = context.formState;
3,997✔
81

82
  const resetObj = useRef(isArray ? [] : {});
3,997✔
83
  const resetObjKey = useRef({});
3,997✔
84

85
  const registerReset = useCallback(
3,997✔
86
    (namePropExt, fnReset, uniqueIDarrayContext) => {
87
      resetObj.current = isArray
349✔
88
        ? [...resetObj.current]
89
        : { ...resetObj.current };
90

91
      if (isArray && typeof resetObj.current[namePropExt] !== "undefined") {
349✔
92
        if (typeof resetObjKey.current[uniqueIDarrayContext] !== "undefined") {
18!
NEW
93
          resetObj.current[namePropExt] = fnReset;
×
94
        } else {
95
          resetObj.current.splice(Number(namePropExt), 0, fnReset);
18✔
96
        }
97
      } else {
98
        resetObj.current[namePropExt] = fnReset;
331✔
99
      }
100

101
      if (typeof uniqueIDarrayContext !== "undefined")
349!
102
        resetObjKey.current[uniqueIDarrayContext] = namePropExt;
349✔
103
    },
104
    []
105
  );
106

107
  const unRegisterReset = useCallback((namePropExt, uniqueIDarrayContext) => {
3,997✔
108
    delete resetObjKey.current[uniqueIDarrayContext];
50✔
109
    if (resetObj.current.constructor === Array) {
50✔
110
      resetObj.current.splice(namePropExt, 1);
49✔
111
    } else {
112
      delete resetObj.current[namePropExt];
1✔
113
    }
114
  }, []);
115

116
  const reset = useCallback(formState => {
3,997✔
117
    valueFieldLastSyncCheck.current = null;
100✔
118
    const initAcc = isArray ? [] : {};
100✔
119
    let obj = Object.keys(resetObj.current).reduce((acc, key) => {
100✔
120
      const value = resetObj.current[key](formState);
207✔
121
      if (value !== undefined) acc[key] = value;
207✔
122
      return acc;
207✔
123
    }, initAcc);
124
    let newValue = applyReducers(obj, memoInitialState.current, formState);
100✔
125
    newValue =
100✔
126
      newValue !== undefined && Object.keys(newValue).length > 0
300✔
127
        ? newValue
128
        : undefined;
129

130
    return newValue;
100✔
131
  }, []);
132

133
  const updateParentProps = useCallback(nextState => {
3,997✔
134
    const newState = applyReducers(nextState, state.current, formState.current);
349✔
135
    const removeProp = Object.keys(newState).length === 0;
349✔
136
    context.changeProp(nameProp.current, newState, removeProp);
349✔
137
  }, []);
138

139
  const changeProp = useCallback((namePropExt, value, removeMe) => {
3,997✔
140
    const nextState = updateState(state.current, {
341✔
141
      value,
142
      nameProp: namePropExt,
143
      removeMe
144
    });
145
    updateParentProps(nextState);
341✔
146
  }, []);
147

148
  const initProp = useCallback(
3,997✔
149
    (namePropExt, value, intialValue, add = true) => {
209✔
150
      const newState = updateState(state.current, {
258✔
151
        value,
152
        nameProp: namePropExt,
153
        add: isMounted.current && add
372✔
154
      });
155

156
      memoInitialState.current = updateState(memoInitialState.current, {
258✔
157
        value: intialValue,
158
        nameProp: namePropExt,
159
        add: isMounted.current && add
372✔
160
      });
161

162
      const reducedState = applyReducers(
258✔
163
        newState,
164
        state.current,
165
        formState.current
166
      );
167

168
      prevState.current = newState;
258✔
169

170
      if (isMounted.current) {
258✔
171
        context.initProp(
114✔
172
          nameProp.current,
173
          reducedState,
174
          memoInitialState.current,
175
          false
176
        );
177
      } else {
178
        state.current = reducedState;
144✔
179
      }
180
    },
181
    []
182
  );
183

184
  const { current: removeProp } = useRef(
3,997✔
185
    (
186
      namePropExt,
187
      { currentState, removeCurrent, initialState, removeInitial },
188
      willUnmount = false
38✔
189
    ) => {
190
      let newStateCurrent = updateState(state.current, {
87✔
191
        value: currentState,
192
        nameProp: namePropExt,
193
        removeMe: removeCurrent
194
      });
195

196
      let newStateInitial = updateState(memoInitialState.current, {
87✔
197
        value: initialState,
198
        nameProp: namePropExt,
199
        removeMe: removeInitial
200
      });
201

202
      if (willUnmount && isArray) {
87✔
203
        newStateCurrent = newStateCurrent.filter(
48✔
204
          (elm, index) => index !== namePropExt
119✔
205
        );
206
        newStateInitial = newStateInitial.filter(
48✔
207
          (elm, index) => index !== namePropExt
119✔
208
        );
209
      }
210

211
      const reducedState = applyReducers(
87✔
212
        newStateCurrent,
213
        state.current,
214
        formState.current
215
      );
216

217
      state.current = reducedState;
87✔
218
      memoInitialState.current = newStateInitial;
87✔
219

220
      const removeCurrentProp = Object.keys(state.current).length === 0;
87✔
221

222
      const removeInitialProp =
223
        Object.keys(memoInitialState.current).length === 0;
87✔
224

225
      context.removeProp(nameProp.current, {
87✔
226
        currentState: state.current,
227
        removeCurrent: removeCurrentProp,
228
        initialState: memoInitialState.current,
229
        removeInitial: removeInitialProp
230
      });
231
    }
232
  );
233

234
  const setValue = useCallback(resolveNextState => {
3,997✔
235
    const nextState =
236
      typeof resolveNextState === "function"
8✔
237
        ? resolveNextState(state.current)
238
        : resolveNextState;
239
    updateParentProps(nextState);
8✔
240
  }, []);
241

242
  const [validators, addValidators, removeValidators] = useValidators(
3,997✔
243
    context,
244
    nameProp,
245
    isMounted
246
  );
247

248
  const [
249
    validatorsAsync,
250
    addValidatorsAsync,
251
    removeValidatorsAsync,
252
    validatorsMapsAsync,
253
    updateValidatorsMap
254
  ] = useValidators(context, nameProp, isMounted, true);
3,997✔
255

256
  const { validationMsg, validationObj, validationFN } = useValidationFunction(
3,997✔
257
    validatorsFuncs
258
  );
259

260
  const [validationFNAsync] = useValidationFunctionAsync(
3,997✔
261
    asyncValidator,
262
    onAsyncValidation
263
  );
264

265
  const triggerSyncValidation = useCallback(
3,997✔
266
    (propagate = true, onSubmit = false) => {
100✔
267
      if (valueFieldLastSyncCheck.current !== state.current) {
51!
268
        if (validationObj.current !== null && (touched || onSubmit)) {
51✔
269
          valueFieldLastSyncCheck.current = state.current;
5✔
270
          const { isValid, checks } = validationObj.current;
5✔
271
          onValidation(checks, isValid);
5✔
272
        }
273
        if (propagate) {
51✔
274
          context?.triggerSyncValidation?.();
50✔
275
        }
276
      }
277
    },
278
    []
279
  );
280

281
  useEffect(() => {
3,997✔
282
    if (context.formStatus === STATUS.ON_RESET) {
883✔
283
      resetSyncErr();
100✔
284
      resetAsyncErr();
100✔
285
    } else if (
783✔
286
      context.formStatus !== STATUS.READY &&
1,339✔
287
      context.formStatus !== STATUS.ON_INIT_ASYNC
288
    ) {
289
      if (
550✔
290
        validationObj.current !== null &&
563✔
291
        context.formStatus === STATUS.ON_SUBMIT
292
      ) {
293
        triggerSyncValidation(false, true);
1✔
294
      }
295

296
      if (validationObj.current !== null && !validationObj.current.isValid) {
550✔
297
        resetAsyncErr();
12✔
298
      }
299
    }
300
  }, [validationMsg.current, context.formStatus]);
301

302
  // used to register async validation Actions
303
  const asyncInitValidation = useRef({});
3,997✔
304
  const registerAsyncInitValidation = useCallback((nameProp, asyncFunc) => {
3,997✔
305
    asyncInitValidation.current[nameProp] = asyncFunc;
7✔
306
  }, []);
307

308
  useEffect(() => {
3,997✔
309
    isMounted.current = true;
178✔
310

311
    if (context.type === "array") {
178✔
312
      context.registerIndex(uniqueIDarrayContext, setNameProp);
65✔
313
    }
314

315
    // Add its own validators
316
    if (validatorsFuncs.length > 0) {
178✔
317
      context.addValidators(nameProp.current, validationFN.current);
5✔
318
    }
319

320
    if (typeof asyncValidator === "function") {
178✔
321
      context.addValidatorsAsync(
4✔
322
        nameProp.current,
323
        validationFNAsync.current,
324
        null
325
      );
326
    }
327

328
    mapFields.current[DISPATCHER_LABEL] = setValue;
178✔
329
    context.updateRegisteredField(nameProp.current, mapFields.current);
178✔
330

331
    // register to parent any initial async Validators to be run ON_INIT
332
    if (Object.keys(asyncInitValidation.current).length > 0) {
178✔
333
      context.registerAsyncInitValidation(
4✔
334
        nameProp.current,
335
        asyncInitValidation.current
336
      );
337
    }
338
    // --- Add its own validators --- //
339

340
    // Add its children validators
341
    if (Object.keys(validators.current).length > 0) {
178✔
342
      context.addValidators(nameProp.current, validators.current);
20✔
343
    }
344

345
    if (Object.keys(validatorsAsync.current).length > 0) {
178✔
346
      context.addValidatorsAsync(
4✔
347
        nameProp.current,
348
        validatorsAsync.current,
349
        validatorsMapsAsync.current
350
      );
351
    }
352
    // --- Add the its children validators --- //
353
    context.registerReset(nameProp.current, reset, uniqueIDarrayContext);
178✔
354

355
    const newState = applyReducers(
178✔
356
      state.current,
357
      prevState.current,
358
      formState.current
359
    );
360

361
    context.initProp(nameProp.current, newState, memoInitialState.current);
178✔
362
    memoState.current = state.current;
178✔
363
    return () => {
178✔
364
      resetSyncErr();
178✔
365
      resetAsyncErr();
178✔
366
      isMounted.current = false;
178✔
367
      state.current = memoState.current;
178✔
368

369
      if (context.stillMounted()) {
178✔
370
        context.unRegisterField(nameProp.current);
21✔
371

372
        // remove its own by validators
373
        if (typeof asyncValidator === "function") {
21!
UNCOV
374
          context.removeValidatorsAsync(
×
375
            nameProp.current,
376
            validationFNAsync.current,
377
            false
378
          );
379
        }
380

381
        if (validatorsFuncs.length > 0) {
21✔
382
          context.removeValidators(nameProp.current, validationFN.current);
1✔
383
        }
384
        // ----- remove its own by validators ----- //
385

386
        // remove validators inerithed by children
387
        if (Object.keys(validators.current).length > 0) {
21✔
388
          context.removeValidators(nameProp.current, validators.current);
2✔
389
        }
390

391
        if (Object.keys(validatorsAsync.current).length > 0) {
21!
UNCOV
392
          context.removeValidatorsAsync(
×
393
            nameProp.current,
394
            validatorsAsync.current,
395
            validatorsMapsAsync.current
396
          );
397
        }
398
        // ----- remove validators inerithed by children ----- //
399
        context.removeProp(
21✔
400
          nameProp.current,
401
          {
402
            removeCurrent: true,
403
            removeInitial: true
404
          },
405
          true
406
        );
407

408
        context.unRegisterReset(nameProp.current, uniqueIDarrayContext);
21✔
409
        if (context.type === "array") {
21✔
410
          context.removeIndex(uniqueIDarrayContext);
20✔
411
        }
412
        unMountIndex();
21✔
413
      }
414
    };
415
  }, []);
416

417
  const childrenIndexes = useRef({});
3,997✔
418

419
  const getIndex = useCallback(idCpm => {
3,997✔
420
    if (childrenIndexes.current[idCpm] === undefined) {
344✔
421
      childrenIndexes.current[idCpm] = null;
182✔
422
    }
423
    return Object.keys(childrenIndexes.current).findIndex(v => v == idCpm);
572✔
424
  }, []);
425

426
  const removeIndex = useCallback(idCpm => {
3,997✔
427
    delete childrenIndexes.current[idCpm];
49✔
428
    Object.keys(childrenIndexes.current).forEach((idField, index) =>
49✔
429
      childrenIndexes.current[idField](index)
122✔
430
    );
431
  }, []);
432

433
  const registerIndex = useCallback((idCpm, fn) => {
3,997✔
434
    childrenIndexes.current[idCpm] = fn;
206✔
435
  }, []);
436

437
  return {
3,997✔
438
    state: state.current, // pass the state of the current context down
439
    formState: context.formState, // pass the global form state down
440
    formStatus: context.formStatus, // pass the global form status down
441
    runAsyncValidation: context.runAsyncValidation,
442
    runSyncValidation: context.runSyncValidation,
443
    unRegisterField,
444
    updateRegisteredField,
445
    registerAsyncInitValidation,
446
    changeProp,
447
    initProp,
448
    removeProp,
449
    stillMounted,
450
    getIndex,
451
    removeIndex,
452
    registerIndex,
453
    type,
454
    addValidators,
455
    removeValidators,
456
    addValidatorsAsync,
457
    removeValidatorsAsync,
458
    updateValidatorsMap,
459
    registerReset,
460
    unRegisterReset,
461
    triggerSyncValidation
462
  };
463
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc