Files
spacedrive/interface/hooks/useCallbackToWatchForm.ts
Vítor Vasconcellos 90a3509469 [ENG-471, ENG-472] Add dry_run parameter to some API routes and related UI changes (#750)
* Add dry_run for `location.create` and `location.addLibrary`

* Add dry_run to IndexerRuleCreateArgs
- Add invalidate_query to create and edit api method of the location route
- Adjust some code to use the new dry_run property

* `AddLocationDialog` and `IndexerRuleEditor` now validate with backend without user interaction
 - Create `useCallbackToWatchForm` to make it easier to watch form changes with an async function that also executes during component mount
 - `method` is now part of the `addLocationDialog` form schema, and a hidden input
 - Add an `index.ts` for hooks and components

* Fix mobile

* Remove redundant type definition

* Add `useCallbackToWatchForm` as an additional hook to `react-hooks/exhaustive-deps`
 - Improve `useCallbackToWatchForm` argument handling, to avoid duplicated references to form

* Fix unnecessary form value updates in `addLocationDialog`
 - Move reset `method` on `path` change logic from the superfluous `useEffect` to `useCallbackToWatchForm` in `addLocationDialog`
 - Improve core API debug logs for `dry_run`
 - Fix incorrect file name for `useCallbackToWatchForm` hook
 - Improve the documentation for `useCallbackToWatchForm`
 - Fix `useCallbackToWatchForm` not ignoring callback errors
 - Fix `useCallbackToWatchForm` ignoring returned Promise from `onWatch`
 - Add basic validation for the `form` argument in `useCallbackToWatchForm`

* Remove superflous if checks
 - generate new core.ts

* Remove DryRunError

* Remove unused import

---------

Co-authored-by: Brendan Allan <brendonovich@outlook.com>
2023-04-29 10:49:51 +00:00

76 lines
2.5 KiB
TypeScript

import { useCallback, useEffect, useRef } from 'react';
import { EventType, FieldPath, FieldValues, UseFormReturn } from 'react-hook-form';
const noop = () => {};
export function useCallbackToWatchForm<S extends FieldValues>(
callback: (
value: S,
info: {
name?: FieldPath<S>;
type?: EventType;
}
) => void | Promise<void>,
deps: [UseFormReturn<S, unknown>, ...React.DependencyList]
): void;
export function useCallbackToWatchForm<S extends FieldValues>(
callback: (
value: S,
info: {
name?: FieldPath<S>;
type?: EventType;
}
) => void | Promise<void>,
deps: React.DependencyList,
form: UseFormReturn<S, unknown>
): void;
/**
* This hook is an async friendly wrapper for useCallback that enables reacting to any form changes.
*
* The callback will be called on the first render, and whenever the form changes, with the current
* form values and the event info regarding the change (empty on first render). If the callback is
* async, or returns a promise, it will wait for the previous callback to finish before executing
* the next one. Any errors thrown by the callback will be ignored.
*
* @param callback - Callback to be called when form changes
* @param deps - Dependency list for the callback
* @param form - Form to watch. If not provided, it will be taken from the first element of the dependency list
*/
export function useCallbackToWatchForm<S extends FieldValues>(
callback: (
value: S,
info: {
name?: FieldPath<S>;
type?: EventType;
}
) => void | Promise<void>,
deps: React.DependencyList,
form?: UseFormReturn<S, unknown>
): void {
if (form == null) form = deps[0] as UseFormReturn<S, unknown>;
if (form == null) throw new Error('Form is not provided');
const { getValues, watch } = form;
if (typeof getValues !== 'function' || typeof watch !== 'function')
throw new Error('Form is not provided');
// Create a promise chain to make sure async callbacks are called in order
const chain = useRef<Promise<true | void>>(Promise.resolve(true));
// Disable lint warning because this hook is a wrapper for useCallback
// eslint-disable-next-line react-hooks/exhaustive-deps
const onWatch = useCallback(callback, deps);
useEffect(() => {
chain.current = chain.current
// If this is the first time, we don't need to wait for a form change
.then((initCheck) => initCheck && onWatch(getValues(), {}))
.finally(noop);
return watch((_, info) => {
chain.current = chain.current.then(() => onWatch(getValues(), info)).finally(noop);
}).unsubscribe;
}, [watch, onWatch, getValues]);
}