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

iusehooks / usetheform / 14198175765

01 Apr 2025 01:30PM UTC coverage: 98.711% (-0.3%) from 99.053%
14198175765

push

github

apangallo
Cypress 11 + tests

583 of 599 branches covered (97.33%)

Branch coverage included in aggregate %.

12 of 12 new or added lines in 3 files covered. (100.0%)

2 existing lines in 1 file now uncovered.

795 of 797 relevant lines covered (99.75%)

1481.58 hits per line

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

97.69
/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 registerReset = useCallback((namePropExt, fnReset) => {
3,997✔
84
    resetObj.current = isArray
349✔
85
      ? [...resetObj.current]
86
      : { ...resetObj.current };
87

88
    if (isArray && typeof resetObj.current[namePropExt] !== "undefined") {
349✔
89
      resetObj.current.splice(Number(namePropExt), 0, fnReset);
18✔
90
    } else {
91
      resetObj.current[namePropExt] = fnReset;
331✔
92
    }
93
  }, []);
94

95
  const unRegisterReset = useCallback(namePropExt => {
3,997✔
96
    if (resetObj.current.constructor === Array) {
50✔
97
      resetObj.current.splice(namePropExt, 1);
49✔
98
    } else {
99
      delete resetObj.current[namePropExt];
1✔
100
    }
101
  }, []);
102

103
  const reset = useCallback(formState => {
3,997✔
104
    valueFieldLastSyncCheck.current = null;
100✔
105
    const initAcc = isArray ? [] : {};
100✔
106
    let obj = Object.keys(resetObj.current).reduce((acc, key) => {
100✔
107
      const value = resetObj.current[key](formState);
207✔
108
      if (value !== undefined) acc[key] = value;
207✔
109
      return acc;
207✔
110
    }, initAcc);
111
    let newValue = applyReducers(obj, memoInitialState.current, formState);
100✔
112
    newValue =
100✔
113
      newValue !== undefined && Object.keys(newValue).length > 0
300✔
114
        ? newValue
115
        : undefined;
116

117
    return newValue;
100✔
118
  }, []);
119

120
  const updateParentProps = useCallback(nextState => {
3,997✔
121
    const newState = applyReducers(nextState, state.current, formState.current);
349✔
122
    const removeProp = Object.keys(newState).length === 0;
349✔
123
    context.changeProp(nameProp.current, newState, removeProp);
349✔
124
  }, []);
125

126
  const changeProp = useCallback((namePropExt, value, removeMe) => {
3,997✔
127
    const nextState = updateState(state.current, {
341✔
128
      value,
129
      nameProp: namePropExt,
130
      removeMe
131
    });
132
    updateParentProps(nextState);
341✔
133
  }, []);
134

135
  const initProp = useCallback(
3,997✔
136
    (namePropExt, value, intialValue, add = true) => {
209✔
137
      const newState = updateState(state.current, {
258✔
138
        value,
139
        nameProp: namePropExt,
140
        add: isMounted.current && add
372✔
141
      });
142

143
      memoInitialState.current = updateState(memoInitialState.current, {
258✔
144
        value: intialValue,
145
        nameProp: namePropExt,
146
        add: isMounted.current && add
372✔
147
      });
148

149
      const reducedState = applyReducers(
258✔
150
        newState,
151
        state.current,
152
        formState.current
153
      );
154

155
      prevState.current = newState;
258✔
156

157
      if (isMounted.current) {
258✔
158
        context.initProp(
114✔
159
          nameProp.current,
160
          reducedState,
161
          memoInitialState.current,
162
          false
163
        );
164
      } else {
165
        state.current = reducedState;
144✔
166
      }
167
    },
168
    []
169
  );
170

171
  const { current: removeProp } = useRef(
3,997✔
172
    (
173
      namePropExt,
174
      { currentState, removeCurrent, initialState, removeInitial },
175
      willUnmount = false
38✔
176
    ) => {
177
      let newStateCurrent = updateState(state.current, {
87✔
178
        value: currentState,
179
        nameProp: namePropExt,
180
        removeMe: removeCurrent
181
      });
182

183
      let newStateInitial = updateState(memoInitialState.current, {
87✔
184
        value: initialState,
185
        nameProp: namePropExt,
186
        removeMe: removeInitial
187
      });
188

189
      if (willUnmount && isArray) {
87✔
190
        newStateCurrent = newStateCurrent.filter(
48✔
191
          (elm, index) => index !== namePropExt
119✔
192
        );
193
        newStateInitial = newStateInitial.filter(
48✔
194
          (elm, index) => index !== namePropExt
119✔
195
        );
196
      }
197

198
      const reducedState = applyReducers(
87✔
199
        newStateCurrent,
200
        state.current,
201
        formState.current
202
      );
203

204
      state.current = reducedState;
87✔
205
      memoInitialState.current = newStateInitial;
87✔
206

207
      const removeCurrentProp = Object.keys(state.current).length === 0;
87✔
208

209
      const removeInitialProp =
210
        Object.keys(memoInitialState.current).length === 0;
87✔
211

212
      context.removeProp(nameProp.current, {
87✔
213
        currentState: state.current,
214
        removeCurrent: removeCurrentProp,
215
        initialState: memoInitialState.current,
216
        removeInitial: removeInitialProp
217
      });
218
    }
219
  );
220

221
  const setValue = useCallback(resolveNextState => {
3,997✔
222
    const nextState =
223
      typeof resolveNextState === "function"
8✔
224
        ? resolveNextState(state.current)
225
        : resolveNextState;
226
    updateParentProps(nextState);
8✔
227
  }, []);
228

229
  const [validators, addValidators, removeValidators] = useValidators(
3,997✔
230
    context,
231
    nameProp,
232
    isMounted
233
  );
234

235
  const [
236
    validatorsAsync,
237
    addValidatorsAsync,
238
    removeValidatorsAsync,
239
    validatorsMapsAsync,
240
    updateValidatorsMap
241
  ] = useValidators(context, nameProp, isMounted, true);
3,997✔
242

243
  const { validationMsg, validationObj, validationFN } = useValidationFunction(
3,997✔
244
    validatorsFuncs
245
  );
246

247
  const [validationFNAsync] = useValidationFunctionAsync(
3,997✔
248
    asyncValidator,
249
    onAsyncValidation
250
  );
251

252
  const triggerSyncValidation = useCallback(
3,997✔
253
    (propagate = true, onSubmit = false) => {
100✔
254
      if (valueFieldLastSyncCheck.current !== state.current) {
51!
255
        if (validationObj.current !== null && (touched || onSubmit)) {
51✔
256
          valueFieldLastSyncCheck.current = state.current;
5✔
257
          const { isValid, checks } = validationObj.current;
5✔
258
          onValidation(checks, isValid);
5✔
259
        }
260
        if (propagate) {
51✔
261
          context?.triggerSyncValidation?.();
50✔
262
        }
263
      }
264
    },
265
    []
266
  );
267

268
  useEffect(() => {
3,997✔
269
    if (context.formStatus === STATUS.ON_RESET) {
883✔
270
      resetSyncErr();
100✔
271
      resetAsyncErr();
100✔
272
    } else if (
783✔
273
      context.formStatus !== STATUS.READY &&
1,339✔
274
      context.formStatus !== STATUS.ON_INIT_ASYNC
275
    ) {
276
      if (
550✔
277
        validationObj.current !== null &&
563✔
278
        context.formStatus === STATUS.ON_SUBMIT
279
      ) {
280
        triggerSyncValidation(false, true);
1✔
281
      }
282

283
      if (validationObj.current !== null && !validationObj.current.isValid) {
550✔
284
        resetAsyncErr();
12✔
285
      }
286
    }
287
  }, [validationMsg.current, context.formStatus]);
288

289
  // used to register async validation Actions
290
  const asyncInitValidation = useRef({});
3,997✔
291
  const registerAsyncInitValidation = useCallback((nameProp, asyncFunc) => {
3,997✔
292
    asyncInitValidation.current[nameProp] = asyncFunc;
7✔
293
  }, []);
294

295
  useEffect(() => {
3,997✔
296
    isMounted.current = true;
178✔
297

298
    if (context.type === "array") {
178✔
299
      context.registerIndex(uniqueIDarrayContext, setNameProp);
65✔
300
    }
301

302
    // Add its own validators
303
    if (validatorsFuncs.length > 0) {
178✔
304
      context.addValidators(nameProp.current, validationFN.current);
5✔
305
    }
306

307
    if (typeof asyncValidator === "function") {
178✔
308
      context.addValidatorsAsync(
4✔
309
        nameProp.current,
310
        validationFNAsync.current,
311
        null
312
      );
313
    }
314

315
    mapFields.current[DISPATCHER_LABEL] = setValue;
178✔
316
    context.updateRegisteredField(nameProp.current, mapFields.current);
178✔
317

318
    // register to parent any initial async Validators to be run ON_INIT
319
    if (Object.keys(asyncInitValidation.current).length > 0) {
178✔
320
      context.registerAsyncInitValidation(
4✔
321
        nameProp.current,
322
        asyncInitValidation.current
323
      );
324
    }
325
    // --- Add its own validators --- //
326

327
    // Add its children validators
328
    if (Object.keys(validators.current).length > 0) {
178✔
329
      context.addValidators(nameProp.current, validators.current);
20✔
330
    }
331

332
    if (Object.keys(validatorsAsync.current).length > 0) {
178✔
333
      context.addValidatorsAsync(
4✔
334
        nameProp.current,
335
        validatorsAsync.current,
336
        validatorsMapsAsync.current
337
      );
338
    }
339
    // --- Add the its children validators --- //
340
    context.registerReset(nameProp.current, reset);
178✔
341

342
    const newState = applyReducers(
178✔
343
      state.current,
344
      prevState.current,
345
      formState.current
346
    );
347

348
    context.initProp(nameProp.current, newState, memoInitialState.current);
178✔
349
    memoState.current = state.current;
178✔
350
    return () => {
178✔
351
      resetSyncErr();
178✔
352
      resetAsyncErr();
178✔
353
      isMounted.current = false;
178✔
354
      state.current = memoState.current;
178✔
355

356
      if (context.stillMounted()) {
178✔
357
        context.unRegisterField(nameProp.current);
21✔
358

359
        // remove its own by validators
360
        if (typeof asyncValidator === "function") {
21!
UNCOV
361
          context.removeValidatorsAsync(
×
362
            nameProp.current,
363
            validationFNAsync.current,
364
            false
365
          );
366
        }
367

368
        if (validatorsFuncs.length > 0) {
21✔
369
          context.removeValidators(nameProp.current, validationFN.current);
1✔
370
        }
371
        // ----- remove its own by validators ----- //
372

373
        // remove validators inerithed by children
374
        if (Object.keys(validators.current).length > 0) {
21✔
375
          context.removeValidators(nameProp.current, validators.current);
2✔
376
        }
377

378
        if (Object.keys(validatorsAsync.current).length > 0) {
21!
UNCOV
379
          context.removeValidatorsAsync(
×
380
            nameProp.current,
381
            validatorsAsync.current,
382
            validatorsMapsAsync.current
383
          );
384
        }
385
        // ----- remove validators inerithed by children ----- //
386
        context.removeProp(
21✔
387
          nameProp.current,
388
          {
389
            removeCurrent: true,
390
            removeInitial: true
391
          },
392
          true
393
        );
394

395
        context.unRegisterReset(nameProp.current);
21✔
396
        if (context.type === "array") {
21✔
397
          context.removeIndex(uniqueIDarrayContext);
20✔
398
        }
399
        unMountIndex();
21✔
400
      }
401
    };
402
  }, []);
403

404
  const childrenIndexes = useRef({});
3,997✔
405

406
  const getIndex = useCallback(idCpm => {
3,997✔
407
    if (childrenIndexes.current[idCpm] === undefined) {
344✔
408
      childrenIndexes.current[idCpm] = null;
182✔
409
    }
410
    return Object.keys(childrenIndexes.current).findIndex(v => v == idCpm);
572✔
411
  }, []);
412

413
  const removeIndex = useCallback(idCpm => {
3,997✔
414
    delete childrenIndexes.current[idCpm];
49✔
415
    Object.keys(childrenIndexes.current).forEach((idField, index) =>
49✔
416
      childrenIndexes.current[idField](index)
122✔
417
    );
418
  }, []);
419

420
  const registerIndex = useCallback((idCpm, fn) => {
3,997✔
421
    childrenIndexes.current[idCpm] = fn;
206✔
422
  }, []);
423

424
  return {
3,997✔
425
    state: state.current, // pass the state of the current context down
426
    formState: context.formState, // pass the global form state down
427
    formStatus: context.formStatus, // pass the global form status down
428
    runAsyncValidation: context.runAsyncValidation,
429
    runSyncValidation: context.runSyncValidation,
430
    unRegisterField,
431
    updateRegisteredField,
432
    registerAsyncInitValidation,
433
    changeProp,
434
    initProp,
435
    removeProp,
436
    stillMounted,
437
    getIndex,
438
    removeIndex,
439
    registerIndex,
440
    type,
441
    addValidators,
442
    removeValidators,
443
    addValidatorsAsync,
444
    removeValidatorsAsync,
445
    updateValidatorsMap,
446
    registerReset,
447
    unRegisterReset,
448
    triggerSyncValidation
449
  };
450
}
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