-
-
Save dbousamra/37d67e2f7b43261d424e7b0d75032d5b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface OnSaveResponse<A> { | |
values: A; | |
existingChangesetId: string | null | undefined; | |
} | |
export interface OnSaveOptions<A> { | |
dirtyValues: A; | |
takeOwnership: boolean; | |
existingChangesetId: string | null | undefined; | |
changesetId: string; | |
} | |
interface AutoSaverProps<A> extends AutoSaveProps<A> { | |
control: Control; | |
} | |
function AutoSaver<A>(props: AutoSaverProps<A>) { | |
const { | |
changesetId, | |
initialExistingChangesetId, | |
control, | |
debounceMs, | |
onSave, | |
diff, | |
combine, | |
} = props; | |
const { watch, reset, getValues, handleSubmit } = useFormContext<A>(); | |
const { isSubmitting } = useFormState({ control }); | |
const [takeOwnership, setTakeOwnership] = React.useState(true); | |
const [existingChangesetId, setExistingChangesetId] = React.useState(initialExistingChangesetId); | |
const lastSubmittedData = React.useRef(getValues()); | |
const debouncedSave = useAsyncDebounce(async () => { | |
if (!isSubmitting) { | |
await handleSubmit(save)(); | |
} | |
}, debounceMs); | |
const save = React.useCallback( | |
async (data: UnpackNestedValue<A>) => { | |
const diffResponse = diff(lastSubmittedData.current as A, data as A); | |
// // If there are no dirty fields, don't bother sending. | |
// // This can occur when a user types, but then backspaces, | |
// // and the values remain the same. | |
if (!diffResponse.isDiff) { | |
return; | |
} | |
// TODO: Is this necessary | |
const values = _.cloneDeep(data as A); | |
const req = { | |
dirtyValues: diffResponse.diff, | |
takeOwnership, | |
existingChangesetId, | |
changesetId, | |
}; | |
const response = await onSave(req); | |
setTakeOwnership(false); | |
setExistingChangesetId(response.existingChangesetId); | |
const serverValues = response.values; | |
const currentFormValues = getValues(); | |
lastSubmittedData.current = serverValues as UnpackNestedValue<A>; | |
// Check to see if the form has changed since we submitted, | |
// by comparing the current form values, with the values | |
// we submitted. | |
// If the form has changed since we sent the request, i.e. a user | |
// has typed since the request was sent issue another save request. | |
// This prevents the form being overridden with values mid-typing. | |
const diffResponseSinceSent = diff(values, currentFormValues as A); | |
// Reset the form to the values we received from the server, merged with | |
// the current diff of the server and what we last sent. | |
reset(combine(serverValues, diffResponseSinceSent.diff) as UnpackNestedValue<DeepPartial<A>>); | |
}, | |
[changesetId, existingChangesetId, takeOwnership, getValues, onSave, reset, diff, combine], | |
); | |
React.useEffect(() => { | |
// eslint-disable-next-line @typescript-eslint/no-misused-promises | |
const subscription = watch(() => debouncedSave()); | |
return () => subscription.unsubscribe(); | |
}, [watch, debouncedSave]); | |
return null; | |
} | |
interface AutoSaveProps<A> { | |
debounceMs: number; | |
startingValues: A; | |
changesetId: string; | |
initialExistingChangesetId: string | null | undefined; | |
onSave: (options: OnSaveOptions<A>) => Promise<OnSaveResponse<A>>; | |
diff: (prev: A, curr: A) => DiffResponse<A>; | |
combine: (prev: A, curr: A) => A; | |
} | |
export function AutoSave<A>(props: React.PropsWithChildren<AutoSaveProps<A>>) { | |
const { | |
startingValues, | |
changesetId, | |
initialExistingChangesetId, | |
onSave, | |
diff, | |
combine, | |
debounceMs, | |
children, | |
} = props; | |
const methods = useForm({ | |
defaultValues: startingValues as any, | |
}); | |
return ( | |
<FormProvider {...methods}> | |
<AutoSaver | |
control={methods.control} | |
startingValues={startingValues} | |
debounceMs={debounceMs} | |
changesetId={changesetId} | |
initialExistingChangesetId={initialExistingChangesetId} | |
onSave={onSave} | |
diff={diff} | |
combine={combine} | |
/> | |
{children} | |
</FormProvider> | |
); | |
} | |
export const AutoSaveMemo = typedMemo(AutoSave); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment