/* eslint-disable react/no-unused-prop-types */ import { useEffect } from 'react' import type { Control, FieldPath, FieldValues, PathValue, UseFormSetValue } from 'react-hook-form' import { useFormState, useWatch } from 'react-hook-form' export type UseFormPersistOptions< TFieldValues extends FieldValues = FieldValues, // eslint-disable-next-line @typescript-eslint/no-explicit-any TContext = any, > = { /** Instance of current form, used to watch value */ form: { control: Control setValue: UseFormSetValue } /** Name of the key how it will be stored in the storage. */ name: string /** * - `sessionStorage`: Will not be avaiable when the user returns later (recommended). * - `localStorage`: Will be available when the user returns later. */ storage?: 'sessionStorage' | 'localStorage' /** Exclude sensitive data from the storage like passwords. */ exclude?: FieldPath[] persist?: FieldPath[] } /** * Will persist any dirty fields and store it in the sessionStorage/localStorage Will restory any * dirty fields when the form is initialized * * Todo: Use wath callback so it won't trigger a rerender * * @deprecated Please use the FormPersist component instead. This method causes INP problems. * @public */ export function useFormPersist(options: UseFormPersistOptions) { const { form, name, storage = 'sessionStorage', exclude = [], persist = [] } = options const { setValue, control } = form const formState = useFormState({ control }) const allFields = useWatch({ control }) const dirtyFieldKeys = Object.keys(formState.dirtyFields) as FieldPath[] // // Get all dirty field values and exclude sensitive data const newValues = Object.fromEntries( dirtyFieldKeys.filter((f) => !exclude.includes(f)).map((field) => [field, allFields[field]]), ) // Amend the values with the values that should always be persisted persist.forEach((persistKey) => { const value = allFields[persistKey] if (value) newValues[persistKey] = value }) // const valuesJson = useMemoObject(newValues) const valuesJson = JSON.stringify(newValues) // Restore changes useEffect(() => { try { if (typeof window === 'undefined') return const storedFormStr = window[storage][name] as string if (!storedFormStr) return const storedValues = JSON.parse(storedFormStr) as FieldValues if (storedValues) { const entries = Object.entries(storedValues) as [FieldPath, PathValue>][] entries.forEach(([entryName, value]) => setValue(entryName, value, { shouldDirty: true, shouldValidate: true }), ) } } catch { // } }, [name, setValue, storage]) // Watch for changes useEffect(() => { try { if (typeof window === 'undefined') return if (valuesJson !== '{}') window[storage][name] = valuesJson else delete window[storage][name] } catch { // } }, [name, storage, valuesJson]) } /** * Please make sure to always include this component at the end of your form because of useWatch * rules: https://react-hook-form.com/docs/usewatch */ export function FormPersist(props: UseFormPersistOptions) { useFormPersist(props) return null }