useControlledFieldArray.ts 1.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
  1. import { set } from 'lodash';
  2. import { useCallback } from 'react';
  3. import { UseFormReturn } from 'react-hook-form';
  4. interface Options<R> {
  5. name: string;
  6. formAPI: UseFormReturn<any>;
  7. defaults?: R[];
  8. // if true, sets `__deleted: true` but does not remove item from the array in values
  9. softDelete?: boolean;
  10. }
  11. export type ControlledField<R> = R & {
  12. __deleted?: boolean;
  13. };
  14. const EMPTY_ARRAY = [] as const;
  15. /*
  16. * react-hook-form's own useFieldArray is uncontrolled and super buggy.
  17. * this is a simple controlled version. It's dead simple and more robust at the cost of re-rendering the form
  18. * on every change to the sub forms in the array.
  19. * Warning: you'll have to take care of your own unique identiifer to use as `key` for the ReactNode array.
  20. * Using index will cause problems.
  21. */
  22. export function useControlledFieldArray<R>(options: Options<R>) {
  23. const { name, formAPI, defaults, softDelete } = options;
  24. const { watch, getValues, reset, setValue } = formAPI;
  25. const fields: Array<ControlledField<R>> = watch(name) ?? defaults ?? EMPTY_ARRAY;
  26. const update = useCallback(
  27. (updateFn: (fields: R[]) => R[]) => {
  28. const values = JSON.parse(JSON.stringify(getValues()));
  29. const newItems = updateFn(fields ?? []);
  30. reset(set(values, name, newItems));
  31. },
  32. [getValues, name, reset, fields]
  33. );
  34. return {
  35. fields,
  36. append: useCallback((values: R) => update((fields) => [...fields, values]), [update]),
  37. remove: useCallback(
  38. (index: number) => {
  39. if (softDelete) {
  40. setValue(`${name}.${index}.__deleted`, true);
  41. } else {
  42. update((items) => {
  43. const newItems = items.slice();
  44. newItems.splice(index, 1);
  45. return newItems;
  46. });
  47. }
  48. },
  49. [update, name, setValue, softDelete]
  50. ),
  51. };
  52. }