UNPKG

form-test-victor

Version:

# Performant forms and wizard library for React

1 lines 80.7 kB
{"version":3,"file":"index.cjs","sources":["../src/shared/types/Validation.ts","../src/Form/contexts/FormContext.ts","../src/shared/util/validation.util.ts","../src/Form/types/WatchMode.ts","../src/Form/types/Validation.ts","../src/Form/contexts/RuleContext.ts","../src/Form/components/Field.tsx","../src/Form/hooks/useRules.ts","../src/Form/hooks/useFieldValidation.ts","../src/Form/components/HiddenField.tsx","../src/Form/components/Rule.tsx","../src/Form/hooks/useForm.ts","../src/Form/hooks/useValues.ts","../src/Form/hooks/useValidations.ts","../src/Wizard/contexts/WizardContext.ts","../src/Form/Form.tsx","../src/Wizard/components/Placeholder.tsx","../src/Wizard/components/Step.tsx","../src/Form/hooks/useFormStatus.ts","../src/Form/hooks/useHiddenField.ts","../src/Wizard/hooks/usePlaceholder.ts","../src/Wizard/hooks/useStepForm.ts","../src/Wizard/hooks/useWizard.ts"],"sourcesContent":["export enum VALIDATION_OUTCOME {\n VALID = 'VALID',\n INVALID = 'INVALID',\n UNDETERMINED = 'UNDETERMINED',\n}\n\nexport interface ValidationStatus {\n status: VALIDATION_OUTCOME,\n message: string | undefined\n}\n\nexport type ValidationStatuses = Record<string, ValidationStatus>;\n","import {\n createContext,\n Dispatch,\n SetStateAction,\n useContext,\n} from 'react';\nimport {\n AnyRecord,\n FieldValue,\n FieldValues,\n ValidationStatus,\n} from '../../shared';\nimport { WATCH_MODE } from '../types/WatchMode';\n\nexport type PublishSubscriber<T extends FieldValue | ValidationStatus> =\n Dispatch<SetStateAction<Record<string, T>>>;\n\nexport interface FormInternal {\n registerField: (\n name: string,\n setValue: (value: FieldValue) => void,\n ) => void,\n unregisterField: (name: string) => void,\n addValueSubscriber: (\n publish: PublishSubscriber<FieldValue>,\n type: WATCH_MODE,\n name?: string[],\n ) => void,\n removeValueSubscriber: (\n publish: PublishSubscriber<FieldValue>,\n type: WATCH_MODE,\n name?: string[],\n ) => void,\n addValidationStatusSubscriber: (\n publish: PublishSubscriber<ValidationStatus>,\n name?: string[],\n ) => void,\n removeValidationStatusSubscriber: (\n publish: PublishSubscriber<ValidationStatus>,\n names?: string[],\n ) => void,\n handleOnChange: (\n name: string,\n value: FieldValue,\n hasFocus: boolean,\n ) => void,\n handleOnBlur: (\n name: string,\n data: AnyRecord | undefined,\n ) => void,\n getFormValuesForNames: (\n names?: string[],\n ) => FieldValues,\n getFormErrorsForNames: (\n names?: string[],\n ) => Record<string, ValidationStatus>,\n updateValidationStatus: (\n name: string,\n validationStatus: ValidationStatus,\n ) => void\n}\n\nexport interface FormContextApi {\n /**\n * DO NOT use outside of library components like Field, useValues, useValidations\n */\n formInternal: FormInternal,\n getFormValues: () => FieldValues,\n resetForm: () => void,\n setFormValues: (newValues: FieldValues, eraseAll?: boolean) => void,\n}\n\nexport const FORM_INTERVAL_DEFAULT: FormInternal = {\n registerField: (): void => {},\n unregisterField: (): void => {},\n addValueSubscriber: (): void => {},\n removeValueSubscriber: (): void => {},\n addValidationStatusSubscriber: (): void => {},\n removeValidationStatusSubscriber: (): void => {},\n handleOnChange: (): void => {},\n handleOnBlur: (): void => {},\n getFormValuesForNames: (): FieldValues => ({}),\n getFormErrorsForNames: (): Record<string, ValidationStatus> => ({}),\n updateValidationStatus: (): void => {},\n};\n\nexport const CONTEXT_FORM_DEFAULT: FormContextApi = {\n formInternal: FORM_INTERVAL_DEFAULT,\n getFormValues: () => ({}),\n resetForm: () => {},\n setFormValues: () => {},\n};\n\nexport const FormContext = createContext(\n CONTEXT_FORM_DEFAULT,\n);\n\n/**\n * Get React Context for form\n */\nexport function useFormContext(): FormContextApi {\n return useContext(FormContext);\n}\n","import { VALIDATION_OUTCOME, ValidationStatuses } from '../types/Validation';\n\nexport function mapValidationStatusesToOutcome(\n validationStatuses: ValidationStatuses,\n): VALIDATION_OUTCOME {\n let hasInvalid = false;\n let hasUndetermined = false;\n\n Object.values(validationStatuses).forEach((validation) => {\n if (validation.status === VALIDATION_OUTCOME.UNDETERMINED) {\n hasUndetermined = true;\n } else if (validation.status === VALIDATION_OUTCOME.INVALID) {\n hasInvalid = true;\n }\n });\n\n if (hasInvalid) {\n return VALIDATION_OUTCOME.INVALID;\n } if (hasUndetermined) {\n return VALIDATION_OUTCOME.UNDETERMINED;\n }\n return VALIDATION_OUTCOME.VALID;\n}\n","export enum WATCH_MODE {\n ON_CHANGE = 'ON_CHANGE',\n ON_BLUR = 'ON_BLUR',\n}\n","import {\n FieldValue,\n VALIDATION_OUTCOME,\n ValidationStatus,\n} from '../../shared';\n\nexport const VALIDATION_STATE_VALID: ValidationStatus = {\n status: VALIDATION_OUTCOME.VALID,\n message: undefined,\n};\nexport const VALIDATION_STATE_UNDETERMINED: ValidationStatus = {\n status: VALIDATION_OUTCOME.UNDETERMINED,\n message: undefined,\n};\n\nexport type Validator = (value: FieldValue) => (boolean | Promise<boolean>);\n","import { createContext, useContext } from 'react';\nimport { Validator } from '../types/Validation';\n\nexport interface RuleContextApi {\n registerRule: (\n testFn: Validator,\n errorMessage: string,\n isDebounced: boolean,\n ) => void,\n unregisterRule: (\n testFn: Validator,\n isDebounced: boolean,\n ) => void,\n}\n\nexport const CONTEXT_RULE_DEFAULT: RuleContextApi = {\n registerRule: () => {},\n unregisterRule: () => {},\n};\n\nexport const RuleContext = createContext(CONTEXT_RULE_DEFAULT);\nexport const useRuleContext = (): RuleContextApi => useContext(RuleContext);\n","import React, {\n ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport {\n AnyRecord,\n FieldValue,\n ValidationStatus,\n} from '../../shared';\nimport { useFormContext } from '../contexts/FormContext';\nimport { useFieldValidation } from '../hooks/useFieldValidation';\nimport { useRules } from '../hooks/useRules';\nimport { RuleContext } from '../contexts/RuleContext';\n\nexport interface FieldRenderProps {\n name: string,\n value: FieldValue,\n onFocus: () => void,\n onBlur: () => void,\n onChange: (e: FieldValue) => void,\n}\n\nexport interface FieldRenderState {\n isPristine: boolean,\n validationStatus: ValidationStatus,\n}\n\nexport interface FieldProps<T extends AnyRecord = AnyRecord> {\n name: string,\n children?: ReactNode,\n render: (fieldProps: FieldRenderProps, fieldState: FieldRenderState) => JSX.Element | null,\n data?: T,\n}\n\n/**\n * Component to integrate a form content in the form system. Every components like input,\n * checkbox has to be rendered by this component\n * @param name - Name of the field, used as identifier\n * @param children Rules (optional)\n * @param render Function to render\n * @param data data transmitted to onUpdateAfterBlur\n * @example\n * ```\n * <Field\n * name=\"foo\"\n * render={(props) => {\n * const { onChange, ...rest } = props;\n * return (<input {...rest} onChange={(e) => onChange(e.target.value)} />);\n * }}\n * >\n * {children}\n * </Field>\n * ```\n */\nexport function Field({\n name, children, render, data,\n}: FieldProps) {\n const {\n formInternal: {\n registerField,\n unregisterField,\n handleOnChange: handleChange,\n handleOnBlur: handleBlur,\n updateValidationStatus,\n },\n } = useFormContext();\n const {\n ruleContext,\n rules,\n debouncedRules,\n } = useRules();\n\n const [value, setValue] = useState<FieldValue>(undefined);\n const [isPristine, setIsPristine] = useState<boolean>(true);\n const validationStatus = useFieldValidation(\n rules,\n debouncedRules,\n value,\n );\n\n const hasFocusRef = useRef(false);\n\n useEffect(() => {\n registerField(name, setValue);\n return () => unregisterField(name);\n }, [name, registerField, unregisterField]);\n\n useEffect(() => {\n if (isPristine && hasFocusRef.current) {\n setIsPristine(false);\n }\n }, [isPristine, value]);\n\n useEffect(() => {\n updateValidationStatus(name, validationStatus);\n }, [name, updateValidationStatus, validationStatus]);\n\n const onChange = useCallback((newValue: FieldValue): void => {\n handleChange(name, newValue, hasFocusRef.current);\n }, [handleChange, name]);\n\n const onBlur = useCallback((): void => {\n hasFocusRef.current = false;\n handleBlur(name, data);\n }, [handleBlur, name, data]);\n\n const onFocus = useCallback(() => {\n hasFocusRef.current = true;\n }, []);\n\n return (\n <RuleContext.Provider value={ruleContext}>\n {render(\n {\n name,\n value,\n onFocus,\n onBlur,\n onChange,\n },\n {\n isPristine,\n validationStatus,\n },\n )}\n {children}\n </RuleContext.Provider>\n );\n}\n\nexport function composeEventHandlers(\n originalEventHandler: (...args: unknown[]) => void,\n formEventHandler: (...args: unknown[]) => void,\n) {\n return (\n (...args: unknown[]): void => {\n if (originalEventHandler) {\n originalEventHandler(...args);\n }\n\n formEventHandler(...args);\n }\n );\n}\n\nField.defaultProps = {\n children: null,\n};\n","import { useCallback, useMemo, useState } from 'react';\nimport { RuleContextApi } from '../contexts/RuleContext';\nimport { Validator } from '../types/Validation';\n\nexport interface Rule {\n validatorFn: Validator,\n errorMessage: string\n}\n\ntype UseRules = ({\n ruleContext: RuleContextApi,\n rules: Rule[],\n debouncedRules: Rule[],\n});\n\nexport function useRules(): UseRules {\n const [rules, setRules] = useState<Rule[]>([]);\n const [debouncedRules, setDebouncedRules] = useState<Rule[]>([]);\n\n const registerRule = useCallback((testFn: Validator, errorMessage: string, isDebounced: boolean): void => {\n if (isDebounced) {\n setDebouncedRules((prev) => [...prev, { validatorFn: testFn, errorMessage }]);\n } else {\n setRules((prev) => [...prev, { validatorFn: testFn, errorMessage }]);\n }\n }, []);\n\n const unregisterRule = useCallback((testFn: Validator, isDebounced: boolean): void => {\n if (isDebounced) {\n setDebouncedRules((previousDebouncedRules) => (\n previousDebouncedRules.filter((rule) => rule.validatorFn !== testFn)\n ));\n } else {\n setRules((previousRules) => (\n previousRules.filter((rule) => rule.validatorFn !== testFn)\n ));\n }\n }, []);\n\n const ruleContext = useMemo(() => ({\n registerRule,\n unregisterRule,\n }), [registerRule, unregisterRule]);\n\n return useMemo(() => ({\n ruleContext,\n rules,\n debouncedRules,\n }), [debouncedRules, ruleContext, rules]);\n}\n","import {\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport {\n FieldValue,\n VALIDATION_OUTCOME,\n ValidationStatus,\n} from '../../shared';\nimport {\n VALIDATION_STATE_UNDETERMINED,\n VALIDATION_STATE_VALID,\n} from '../types/Validation';\nimport { Rule } from './useRules';\n\nconst DEBOUNCE_TIME = 500;\n\nexport function useFieldValidation(\n rules: Rule[],\n debouncedRules: Rule[],\n value: FieldValue,\n): ValidationStatus {\n const numberOfRules = rules.length + debouncedRules.length;\n const [\n validationStatus,\n setValidationStatus,\n ] = useState<ValidationStatus>(\n numberOfRules\n ? VALIDATION_STATE_UNDETERMINED\n : VALIDATION_STATE_VALID,\n );\n const metaState = useRef({\n countRulesResolved: 0,\n phaseId: 0,\n hasError: false,\n });\n\n const testRules = useCallback((\n rulesToTest: Rule[],\n fieldValue: FieldValue,\n phaseId: number,\n ) => {\n rulesToTest.forEach(({ validatorFn, errorMessage }) => {\n if (metaState.current.hasError) {\n return;\n }\n Promise.resolve(validatorFn(fieldValue)).then((isValid) => {\n /*\n Ensure that the validation corresponds to the latest value with its\n matching phaseId.\n For example, if this loop is still running but phaseId has changed\n since, we do not update setValidationStatus\n */\n if (phaseId !== metaState.current.phaseId) {\n return;\n }\n metaState.current.countRulesResolved += 1;\n\n if (!isValid && !metaState.current.hasError) {\n throw new Error(errorMessage);\n }\n /*\n If we run the last rule, and no error was thrown,\n update status to VALIDATION_STATE_VALID\n */\n if (isValid && metaState.current.countRulesResolved === numberOfRules) {\n setValidationStatus(VALIDATION_STATE_VALID);\n }\n }).catch(({ message }) => {\n const status: ValidationStatus = { status: VALIDATION_OUTCOME.INVALID, message };\n metaState.current.hasError = true;\n setValidationStatus(status);\n });\n });\n }, [numberOfRules]);\n\n useEffect(() => {\n // When we don't have rules we do nothing\n if (!numberOfRules) {\n setValidationStatus(VALIDATION_STATE_VALID);\n return () => {};\n }\n setValidationStatus(VALIDATION_STATE_UNDETERMINED);\n\n const { current } = metaState;\n // On each render, we increment phaseId,\n // doing so we ensure actions are done on right value\n current.phaseId += 1;\n current.countRulesResolved = 0;\n current.hasError = false;\n const currentPhaseId = current.phaseId;\n\n testRules(rules, value, currentPhaseId);\n const token = setTimeout(() => {\n testRules(debouncedRules, value, currentPhaseId);\n }, DEBOUNCE_TIME);\n return () => clearTimeout(token);\n }, [debouncedRules, numberOfRules, rules, testRules, value]);\n\n return validationStatus;\n}\n","import React, {\n ReactNode,\n} from 'react';\nimport { Field } from './Field';\n\ninterface HiddenFieldProps {\n name: string,\n children?: ReactNode,\n}\n\n/**\n * HiddenField, need to be used with useHiddenField hook\n * @param name - Field name\n * @param children - Rules (optional)\n * @example\n * ```\n * const fooHiddenField = useHiddenField('foo');\n * return (\n * <HiddenField {...fooHiddenField} />\n * )\n * ```\n */\nexport function HiddenField(props: HiddenFieldProps) {\n return (\n <Field {...props} render={() => null} />\n );\n}\n\nHiddenField.defaultProps = {\n children: null,\n};\n","import { ReactNode, useEffect } from 'react';\nimport { useRuleContext } from '../contexts/RuleContext';\nimport { Validator } from '../types/Validation';\n\ninterface RuleProps {\n validationFn: Validator,\n message: string,\n children?: ReactNode,\n isDebounced?: boolean,\n}\n\n/**\n *\n * @param message - Message when error is true\n * @param children - Children\n * @param validationFn - Function run for the validation\n * @param isDebounced - Is validation function run with debounce\n * @example\n * ```\n * <TextField name=\"foo\">\n * <Rule\n * message=\"REQUIRED\"\n * validationFn={(value) => !!value && value !== ''}\n * />\n * </TextField>\n * ```\n */\nexport function Rule({\n message, children, validationFn, isDebounced = false,\n}: RuleProps) {\n const { registerRule, unregisterRule } = useRuleContext();\n\n useEffect(() => {\n registerRule(validationFn, message, isDebounced);\n return () => unregisterRule(validationFn, isDebounced);\n }, [isDebounced, message, registerRule, unregisterRule, validationFn]);\n\n return children;\n}\n\nRule.defaultProps = {\n children: null,\n isDebounced: false,\n};\n","import { useCallback, useMemo, useRef } from 'react';\nimport {\n FieldValues,\n FieldValue,\n VALIDATION_OUTCOME,\n ValidationStatus,\n AnyRecord,\n} from '../../shared';\nimport { FormContextApi, PublishSubscriber } from '../contexts/FormContext';\nimport { WATCH_MODE } from '../types/WatchMode';\nimport { VALIDATION_STATE_UNDETERMINED } from '../types/Validation';\n\n/**\n * Generate methods for form\n * @return {FormContextApi}\n * @example\n * ```\n * const form = useForm();\n * const { setFormValues } = form;\n *\n * useEffect(() => {\n * setFormValues({\"foo\": \"foo\", \"bar\": \"bar\"})\n * },[])\n *\n * return(\n * <Form form={form}>\n * <TextInput name=\"name\" />\n * </Form>\n * )\n * ```\n */\n\nexport interface UseFormOptions {\n onUpdateAfterBlur?: (\n name: string,\n value: FieldValue,\n data: AnyRecord,\n formPartial: Pick<FormContextApi, 'getFormValues' | 'setFormValues'>,\n ) => Promise<void> | void\n}\n\nexport function useForm({ onUpdateAfterBlur }: UseFormOptions = {}): FormContextApi {\n // -- TYPES --\n interface FieldState {\n name: string,\n value: FieldValue,\n validation: ValidationStatus,\n isRegistered: boolean,\n }\n\n type EachFieldCallback = (infos: FieldState) => void;\n\n type FormSubscriber<T extends FieldValue | ValidationStatus> =\n Record<string, Set<PublishSubscriber<T>>>;\n\n interface FormSubscribersScope<T extends FieldValue | ValidationStatus> {\n global: Set<PublishSubscriber<T>>,\n scoped: FormSubscriber<T>,\n }\n\n const { current: globalTimeout } = useRef<Record<string, Record<string, NodeJS.Timeout>>>({\n errors: {},\n values: {},\n });\n\n // -- FORM STATE --\n const { current: formState } = useRef<Record<string, FieldState>>({});\n const fieldNameOnChangeByUserRef = useRef<string | undefined>();\n\n /**\n * Run function for every field of FormState with FormFieldState on parameters\n * @param fn\n */\n const eachField = useCallback((fn: EachFieldCallback): void => {\n Object.keys(formState).forEach((fieldName) => {\n fn(formState[fieldName]);\n });\n }, [formState]);\n\n // -- SUBSCRIPTION --\n\n type FormSubscribersRef<T extends FieldValue | ValidationStatus> =\n Record<WATCH_MODE, FormSubscribersScope<T>>;\n\n const { current: formValuesSubscribers } = useRef<FormSubscribersRef<FieldValue>>({\n [WATCH_MODE.ON_CHANGE]: {\n global: new Set<PublishSubscriber<FieldValue>>(),\n scoped: {},\n },\n [WATCH_MODE.ON_BLUR]: {\n global: new Set<PublishSubscriber<FieldValue>>(),\n scoped: {},\n },\n });\n\n const { current: formErrorsSubscribers } = useRef<FormSubscribersRef<ValidationStatus>>({\n [WATCH_MODE.ON_CHANGE]: {\n global: new Set<PublishSubscriber<ValidationStatus>>(),\n scoped: {},\n },\n [WATCH_MODE.ON_BLUR]: {\n global: new Set<PublishSubscriber<ValidationStatus>>(),\n scoped: {},\n },\n });\n\n // -- EXPORTS --\n\n /**\n * Get values registered for an array of field names\n * @param names Array of field names\n */\n const getFormValuesForNames = useCallback((\n names?: string[],\n ): FieldValues => {\n const namesArr = names || Object.keys(formState);\n const values: FieldValues = {};\n namesArr.forEach((name) => {\n if (formState[name]?.isRegistered) {\n values[name] = formState[name].value;\n }\n });\n return values;\n }, [formState]);\n\n /**\n * Get values for an array of field names. Return only errors of registered fields\n * @param names Array of field names\n */\n const getFormErrorsForNames = useCallback((\n names?: string[],\n ): Record<string, ValidationStatus> => {\n const namesArr = names || Object.keys(formState);\n const validations: Record<string, ValidationStatus> = {};\n namesArr.forEach((name) => {\n if (formState[name]?.isRegistered) {\n validations[name] = formState[name].validation;\n }\n });\n return validations;\n }, [formState]);\n\n /**\n * Return values of displayed fields\n * @example\n * ```\n * const { getFormValues } = useForm();\n * console.log(getFormValues());\n * // {\"input1\": \"value1\", \"input1\": \"value1\",}\n * ```\n */\n const getFormValues = useCallback<() => FieldValues>(\n () => getFormValuesForNames(), [getFormValuesForNames]);\n /**\n * Update every subscriber of value for a given field name\n * @param name Field name\n * @param watchMode Type of subscriber updated\n */\n const updateValueSubscribers = useCallback((name: string, watchMode: WATCH_MODE): void => {\n // Update watcher for this field name\n if (formValuesSubscribers[watchMode].scoped[name]) {\n formValuesSubscribers[watchMode].scoped[name]\n .forEach((publish) => {\n /*\n * publish is the function given by react hook useState:\n * `const [example, setExample] = useState(() => {})` It's `example`\n *\n * With the hook we can have the previous values like setExample((previousValues) => ... )\n * Here, only value of the field updated must be change so we get the previous object\n * and we change value with key [name] with the new value\n */\n publish((previous) => ({\n ...previous,\n [name]: formState[name].isRegistered ? formState[name].value : undefined,\n }));\n });\n }\n\n if (formValuesSubscribers[watchMode].global.size) {\n /*\n If there is a global subscriber and lot of fields are render, avoid\n spam of refresh on this subscriber\n */\n clearTimeout(globalTimeout.values[watchMode]);\n globalTimeout.values[watchMode] = setTimeout(() => {\n const formValues = getFormValues();\n // Update global watcher\n formValuesSubscribers[watchMode].global.forEach((publish) => {\n // To be simpler, send value returned by getFormValues method\n publish(formValues);\n });\n }, 0);\n }\n }, [formValuesSubscribers, formState, getFormValues, globalTimeout]);\n\n /**\n * Update every subscriber of error for a given field name\n * @param name Field name\n * @param watchMode Type of subscriber updated\n */\n const updateErrorSubscribers = useCallback((name: string, watchMode: WATCH_MODE): void => {\n // Update watcher for this field name\n if (formErrorsSubscribers[watchMode].scoped[name]) {\n formErrorsSubscribers[watchMode].scoped[name]\n .forEach((publish) => {\n /*\n * publish is the function given by react hook useState:\n * `const [example, setExample] = useState(() => {})` It's `example`\n *\n * With the hook we can have the previous values like setExample((previousValues) => ... )\n * Here, only value of the field updated must be change so we get the previous object\n * and we change value with key [name] with the new value\n */\n publish((previous) => {\n const previousCopy = previous;\n if (!formState[name].isRegistered) {\n delete previousCopy[name];\n } else {\n previousCopy[name] = formState[name].validation;\n }\n return previousCopy;\n });\n });\n }\n if (formErrorsSubscribers[watchMode].global.size) {\n /*\n If there is a global subscriber and lot of fields are render, avoid\n spam of refresh on this subscriber\n */\n clearTimeout(globalTimeout.errors[watchMode]);\n globalTimeout.errors[watchMode] = setTimeout(() => {\n const formValues = getFormErrorsForNames();\n // Update global watcher\n formErrorsSubscribers[watchMode].global.forEach((publish) => {\n // To be simpler, send value returned by getFormValues method\n publish(formValues);\n });\n }, 0);\n }\n }, [formErrorsSubscribers, formState, getFormErrorsForNames, globalTimeout]);\n\n /**\n * Update all types of value subscribers for a given field name\n * @param name nameField name\n */\n const updateValueForAllTypeOfSubscribers = useCallback((name: string): void => {\n updateValueSubscribers(name, WATCH_MODE.ON_CHANGE);\n updateValueSubscribers(name, WATCH_MODE.ON_BLUR);\n }, [updateValueSubscribers]);\n\n /**\n * Update all types of error subscribers for a given field name\n * @param name nameField name\n */\n const updateErrorForAllTypeOfSubscribers = useCallback((name: string): void => {\n updateErrorSubscribers(name, WATCH_MODE.ON_CHANGE);\n updateErrorSubscribers(name, WATCH_MODE.ON_BLUR);\n }, [updateErrorSubscribers]);\n\n /**\n * Unregister field. Data is still in state after un-registration\n * but field is marked as not displayed.\n * @param name Field name\n */\n const unregisterField = useCallback((name: string): void => {\n formState[name].isRegistered = false;\n updateValueForAllTypeOfSubscribers(name);\n updateErrorForAllTypeOfSubscribers(name);\n }, [formState, updateValueForAllTypeOfSubscribers, updateErrorForAllTypeOfSubscribers]);\n\n /**\n * Add new subscriber for given fields\n * @param publish Function trigger on value change\n * @param watchMode Type of watcher\n * @param formSubscriber List of subscribers updated\n * @param names Field names\n */\n const addSubscriber = useCallback(<T extends FieldValue | ValidationStatus>(\n publish: PublishSubscriber<T>,\n watchMode: WATCH_MODE,\n formSubscriber: FormSubscribersRef<T>,\n names?: string[],\n ): void => {\n const formSubscriberCopy = formSubscriber;\n\n if (!names) {\n formSubscriberCopy[watchMode].global.add(publish);\n return;\n }\n names.forEach((name) => {\n if (!formSubscriberCopy[watchMode].scoped[name]) {\n formSubscriberCopy[watchMode].scoped[name] = new Set();\n }\n formSubscriberCopy[watchMode].scoped[name].add(publish);\n });\n }, []);\n\n /**\n * Add new subscriber of values for given fields\n * @param publish Function trigger on value change\n * @param watchMode Type of watcher\n * @param names Field names\n */\n const addValueSubscriber = useCallback((\n publish: PublishSubscriber<FieldValue>,\n watchMode: WATCH_MODE,\n names?: string[],\n ): void => {\n addSubscriber<FieldValue>(\n publish,\n watchMode,\n formValuesSubscribers,\n names,\n );\n publish(getFormValuesForNames(names));\n }, [addSubscriber, formValuesSubscribers, getFormValuesForNames]);\n\n /**\n * Add new subscriber of validation status for given fields\n * @param publish Function trigger on error change\n * @param names Field names\n */\n const addValidationStatusSubscriber = useCallback((\n publish: PublishSubscriber<ValidationStatus>,\n names?: string[],\n ): void => {\n addSubscriber<ValidationStatus>(\n publish,\n WATCH_MODE.ON_CHANGE,\n formErrorsSubscribers,\n names,\n );\n publish(getFormErrorsForNames(names));\n }, [addSubscriber, formErrorsSubscribers, getFormErrorsForNames]);\n\n /**\n * Register field in the form state.\n * We cannot have more than one active field for a name.\n * @param name Field name\n * @param setValue Function to change Field value and trigger render\n */\n const registerField = useCallback((name: string, setValue: (value: FieldValue) => void): void => {\n const previousValueStored = formState[name]?.value;\n if (formState[name]?.isRegistered) {\n throw new Error(`Attempting to register field \"${name}\" a second time`);\n }\n\n formState[name] = {\n name,\n isRegistered: true,\n value: previousValueStored,\n validation: VALIDATION_STATE_UNDETERMINED,\n };\n\n /*\n Add subscriber has to be setValue saving array. Here, the publisher needs only the first value\n (and the only one) returned in values so we created a function to do the mapping\n */\n addValueSubscriber(\n (publish) => {\n const values = typeof publish === 'function' ? publish({}) : publish;\n setValue(values?.[name]);\n },\n WATCH_MODE.ON_CHANGE,\n [name],\n );\n updateValueForAllTypeOfSubscribers(name);\n }, [formState, addValueSubscriber, updateValueForAllTypeOfSubscribers]);\n\n /**\n * Remove subscriber for given fields in the list of subscribers given\n * @param publish Function used to be triggered on value change\n * @param watchMode Type of watcher\n * @param formSubscriber List of subscribers to update\n * @param names Field names\n */\n const removeSubscriber = useCallback(<T extends FieldValue | ValidationStatus>(\n publish: PublishSubscriber<T>,\n watchMode: WATCH_MODE,\n formSubscriber: FormSubscribersRef<T>,\n names?: string[],\n ): void => {\n if (!names) {\n formSubscriber[watchMode].global.delete(publish);\n return;\n }\n names.forEach((name: string) => {\n formSubscriber[watchMode].scoped[name].delete(publish);\n });\n }, []);\n\n /**\n * Remove values subscriber for given fields\n * @param publish Function used to be triggered on value change\n * @param watchMode Type of watcher\n * @param names Field names\n */\n const removeValueSubscriber = useCallback((\n publish: PublishSubscriber<FieldValue>,\n watchMode: WATCH_MODE,\n names?: string[],\n ): void => {\n removeSubscriber<FieldValue>(\n publish,\n watchMode,\n formValuesSubscribers,\n names,\n );\n }, [formValuesSubscribers, removeSubscriber]);\n\n /**\n * Remove validation status subscriber for given fields\n * @param publish Function used to be triggered on value change\n * @param names Field names\n */\n const removeValidationStatusSubscriber = useCallback((\n publish: PublishSubscriber<ValidationStatus>,\n names?: string[],\n ): void => {\n removeSubscriber<ValidationStatus>(\n publish,\n WATCH_MODE.ON_CHANGE,\n formErrorsSubscribers,\n names,\n );\n }, [formErrorsSubscribers, removeSubscriber]);\n\n /**\n * Change values of the form\n * @param newValues list of new values\n * @param eraseAll If true, reset all values associated to a registered field\n */\n const setFormValues = useCallback((\n newValues: FieldValues,\n eraseAll = false,\n ): void => {\n if (eraseAll) {\n eachField(({ name }) => {\n formState[name].value = undefined;\n updateValueForAllTypeOfSubscribers(name);\n });\n }\n\n Object.keys(newValues).forEach((name) => {\n // If the field is already stored, only update the value\n if (formState[name]) {\n formState[name].value = newValues[name];\n } else {\n // Else, save a new line in the context for the given name. When the field will be\n // registered later, he will have access to the value\n formState[name] = {\n name,\n isRegistered: false,\n value: newValues[name],\n validation: VALIDATION_STATE_UNDETERMINED,\n };\n }\n updateValueForAllTypeOfSubscribers(name);\n });\n }, [eachField, formState, updateValueForAllTypeOfSubscribers]);\n\n /**\n * Handle onChange action trigger for a field\n * DO NOT use outside of field\n * @param name Field name\n * @param value New value\n * @param isUserInput Is changed is from user input\n */\n const handleOnChange = useCallback((name: string, value: FieldValue, hasFocus: boolean): void => {\n if (formState[name].value === value) {\n return;\n }\n // Keep field name to know on blur if the field has been updated by user input\n if (hasFocus) {\n fieldNameOnChangeByUserRef.current = name;\n }\n // Update value in store\n formState[name].value = value;\n updateValueSubscribers(name, WATCH_MODE.ON_CHANGE);\n }, [formState, updateValueSubscribers]);\n\n /**\n * Handle onBlur action trigger for a field\n * DO NOT use outside of field\n * @param name Field name updated\n * @param data Data injected in onUpdateAfterBlur\n */\n const handleOnBlur = useCallback(async (name: string, data: AnyRecord = {}): Promise<void> => {\n updateValueSubscribers(name, WATCH_MODE.ON_BLUR);\n if (\n onUpdateAfterBlur\n && fieldNameOnChangeByUserRef.current === name\n && formState[name].validation.status === VALIDATION_OUTCOME.VALID\n ) {\n await onUpdateAfterBlur(name, formState[name].value, data, { getFormValues, setFormValues });\n }\n fieldNameOnChangeByUserRef.current = undefined;\n }, [updateValueSubscribers, onUpdateAfterBlur, formState, getFormValues, setFormValues]);\n\n /**\n * Update validation status for a given field\n * @param name Field name\n * @param validationStatus New status\n */\n const updateValidationStatus = useCallback((\n name: string,\n validationStatus: ValidationStatus,\n ): void => {\n formState[name].validation = validationStatus;\n updateErrorSubscribers(name, WATCH_MODE.ON_CHANGE);\n }, [formState, updateErrorSubscribers]);\n\n /**\n * Reset form value and trigger form rerender\n * @example\n * ```\n * const { resetForm } = useForm();\n * const handleChange = () => resetForm();\n * ```\n */\n const resetForm = useCallback((): void => setFormValues({}, true), [setFormValues]);\n\n return useMemo<FormContextApi>(() => ({\n formInternal: {\n registerField,\n unregisterField,\n addValueSubscriber,\n removeValueSubscriber,\n addValidationStatusSubscriber,\n removeValidationStatusSubscriber,\n handleOnChange,\n handleOnBlur,\n getFormValuesForNames,\n getFormErrorsForNames,\n updateValidationStatus,\n },\n getFormValues,\n resetForm,\n setFormValues,\n }), [\n registerField,\n unregisterField,\n addValueSubscriber,\n removeValueSubscriber,\n addValidationStatusSubscriber,\n removeValidationStatusSubscriber,\n handleOnChange,\n handleOnBlur,\n getFormValuesForNames,\n getFormErrorsForNames,\n updateValidationStatus,\n getFormValues,\n resetForm,\n setFormValues,\n ]);\n}\n","import {\n useEffect,\n useState,\n} from 'react';\nimport { FieldValues } from '../../shared';\nimport {\n CONTEXT_FORM_DEFAULT,\n FormContextApi,\n useFormContext,\n} from '../contexts/FormContext';\nimport { WATCH_MODE } from '../types/WatchMode';\n\n/**\n * Hook to watch values.\n * If names is not defined, watch all values\n * @param watchMode OnBlur or OnChange\n * @param names (optional) Field names\n * @param form (optional) form to use. If it's not given, form context is used.\n */\nfunction useValues(\n watchMode: WATCH_MODE,\n names?: string[],\n form?: FormContextApi,\n): FieldValues {\n const formContext = useFormContext();\n const {\n formInternal: {\n addValueSubscriber,\n removeValueSubscriber,\n },\n } = form || formContext;\n const [currentValues, setCurrentValues] = useState<FieldValues>({});\n\n if (!form && formContext === CONTEXT_FORM_DEFAULT) {\n throw new Error('No form context could be found while calling \"useValues\".');\n }\n\n useEffect(() => {\n addValueSubscriber(setCurrentValues, watchMode, names);\n return () => removeValueSubscriber(setCurrentValues, watchMode, names);\n // names is transformed to string to ensure consistant ref\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [addValueSubscriber, removeValueSubscriber, watchMode, names?.join()]);\n\n return currentValues;\n}\n\n/**\n * Watch field with updates onChange\n * If `names` is not specified, watch all values\n * @param names (optional) Field names\n * @param form (optional) form to use\n * @return\n * ```\n * const { foo } = useOnChangeValues(['foo']);\n * useEffect(() => {\n * console.log(foo);\n * },[])\n * ```\n */\nexport function useOnChangeValues(\n names?: string[],\n form?: FormContextApi,\n): FieldValues {\n return useValues(WATCH_MODE.ON_CHANGE, names, form);\n}\n\n/**\n * Watch field with updates onBlur\n * If `names` is not specified, watch all values\n * @param names (optional) Field names\n * @param form (optional) form to use\n * @return\n * ```\n * const { foo } = useOnBlurValues(['foo']);\n * useEffect(() => {\n * console.log(foo);\n * },[])\n * ```\n */\nexport function useOnBlurValues(\n names?: string[],\n form?: FormContextApi,\n): FieldValues {\n return useValues(WATCH_MODE.ON_BLUR, names, form);\n}\n","import {\n useEffect,\n useState,\n} from 'react';\nimport { ValidationStatuses } from '../../shared';\nimport {\n CONTEXT_FORM_DEFAULT,\n FormContextApi,\n useFormContext,\n} from '../contexts/FormContext';\n\n/**\n * Build hook to watch values.\n * If names is not defined, watch all values\n * @param names (optional) Field names\n * @param form (optional) form to use. If it's not given, form context is used.\n * @return\n * ```\n * const { foo } = useValidations(['foo']);\n * useEffect(() => {\n * console.log(foo);\n * },[])\n * ```\n */\nexport function useValidations(names?: string[], form?: FormContextApi): ValidationStatuses {\n const formContext = useFormContext();\n const {\n formInternal: {\n addValidationStatusSubscriber,\n removeValidationStatusSubscriber,\n },\n } = form || formContext;\n const [currentValidations, setCurrentValidations] = useState<ValidationStatuses>({});\n\n if (!form && formContext === CONTEXT_FORM_DEFAULT) {\n throw new Error('No form context could be found while calling \"useValidations\".');\n }\n\n useEffect(() => {\n addValidationStatusSubscriber(setCurrentValidations, names);\n return () => removeValidationStatusSubscriber(setCurrentValidations, names);\n }, [addValidationStatusSubscriber, names?.join(), removeValidationStatusSubscriber]);\n\n return currentValidations;\n}\n","import {\n createContext,\n Dispatch,\n ReactNode,\n SetStateAction,\n useContext,\n} from 'react';\nimport {\n FieldValues,\n VALIDATION_OUTCOME,\n} from '../../shared';\nimport {\n PlaceholderContentSetter,\n StepValidator,\n} from '../types';\n\nexport type ValidationStatusesSetter = Dispatch<SetStateAction<VALIDATION_OUTCOME>>;\n\nexport interface WizardContextApi {\n steps: string[],\n currentStep: string | undefined,\n registerStep: (\n name: string,\n validationFn?: StepValidator,\n noFooter?: boolean,\n title?: string,\n ) => void,\n unregisterStep: (name: string) => void,\n handleOnNext: () => Promise<void>,\n handleOnPrevious: () => void,\n updatePlaceholderContent: (placement: string, children: ReactNode) => void,\n resetPlaceholderContent: (placement?: string) => void,\n registerPlaceholder: (\n placeholderContentSetter: PlaceholderContentSetter,\n stepStatusSetter: ValidationStatusesSetter,\n ) => void,\n unregisterPlaceholder: (\n placeholderContentSetter: PlaceholderContentSetter,\n stepStatusSetter: ValidationStatusesSetter,\n ) => void,\n isLastStep: boolean,\n isFirstStep: boolean,\n hasNoFooter: boolean,\n stepStatusSetter: ValidationStatusesSetter,\n isStepReady: boolean,\n stepsTitles: {name: string, title: string | undefined}[],\n setIsStepReady: Dispatch<SetStateAction<boolean>>,\n setValuesGetterForCurrentStep: (\n stepValuesGetter: () => FieldValues | undefined\n ) => void,\n getValuesOfCurrentStep: () => FieldValues | undefined,\n getValuesOfStep: (stepName: string) => FieldValues | undefined,\n getValuesOfSteps: () => Record<string, FieldValues | undefined>\n}\n\nexport const CONTEXT_WIZARD_DEFAULT: WizardContextApi = {\n steps: [],\n currentStep: undefined,\n registerStep: () => {},\n unregisterStep: () => {},\n handleOnNext: async () => {},\n handleOnPrevious: () => {},\n updatePlaceholderContent: () => {},\n resetPlaceholderContent: () => {},\n registerPlaceholder: () => {},\n unregisterPlaceholder: () => {},\n isLastStep: false,\n isFirstStep: false,\n hasNoFooter: true,\n stepStatusSetter: () => {},\n isStepReady: false,\n stepsTitles: [],\n setIsStepReady: () => {},\n setValuesGetterForCurrentStep: () => {},\n getValuesOfCurrentStep: () => undefined,\n getValuesOfStep: () => undefined,\n getValuesOfSteps: () => ({}),\n};\n\nexport const WizardContext = createContext(\n CONTEXT_WIZARD_DEFAULT,\n);\nexport function useWizardContext(): WizardContextApi {\n return useContext(WizardContext);\n}\n","import React, { ReactNode } from 'react';\nimport { FormContext, FormContextApi } from './contexts/FormContext';\n\ninterface FormProps {\n children: ReactNode,\n form: FormContextApi,\n}\n\n/**\n * Context to handle Form. Similar to <form> but using React Context\n * @param children - Children\n * @param form - Form from useForm hook\n * @example\n * ```\n * const form = useForm():\n *\n * return (\n * <Form form={form}>\n * <TextField name=\"foo\" />\n * </Form>\n * )\n * ```\n */\nexport function Form({ children, form }: FormProps) {\n return (\n <FormContext.Provider value={form}>\n {children}\n </FormContext.Provider>\n );\n}\n","import {\n ReactNode,\n useEffect,\n} from 'react';\nimport { useWizardContext } from '../contexts/WizardContext';\n\nexport interface PlaceholderProps {\n placement: string,\n children: ReactNode,\n}\n\nexport function Placeholder({\n placement,\n children,\n}: PlaceholderProps) {\n const {\n updatePlaceholderContent,\n resetPlaceholderContent,\n } = useWizardContext();\n\n useEffect(() => {\n updatePlaceholderContent(placement, children);\n return () => resetPlaceholderContent(placement);\n }, [updatePlaceholderContent, placement, children, resetPlaceholderContent]);\n return null;\n}\n","import {\n ReactNode,\n useEffect,\n} from 'react';\nimport {\n CONTEXT_WIZARD_DEFAULT,\n useWizardContext,\n} from '../contexts/WizardContext';\nimport { StepValidator } from '../types';\n\nexport interface StepProps {\n children: ReactNode,\n name: string,\n onNext?: StepValidator,\n noFooter?: boolean,\n title?: string,\n}\n\n/**\n * Step used in Wizard\n * @param children Node\n * @param name Step name\n * @param onNext (optional) Function ran at click on next\n * @param noFooter (optional) has footer if not specified`\n * @param title Title of the step\n * @example\n * ```\n * <Wizard>\n * <Step name=\"StepOne\" >\n * 1\n * </Step>\n * <Step name=\"StepTwo\" >\n * 2\n * </Step>\n * </Wizard\n * ``\n */\nexport function Step({\n children,\n name,\n onNext,\n noFooter,\n title,\n}: StepProps) {\n const wizard = useWizardContext();\n\n const {\n registerStep,\n unregisterStep,\n currentStep,\n setIsStepReady,\n } = wizard;\n\n if (wizard === CONTEXT_WIZARD_DEFAULT) {\n throw new Error('Step not in wizard');\n }\n\n useEffect(() => {\n registerStep(name, onNext, noFooter, title);\n return () => unregisterStep(name);\n }, [registerStep, unregisterStep, name, onNext, noFooter, title]);\n\n useEffect(() => {\n if (currentStep === name) {\n setIsStepReady(true);\n }\n }, [currentStep, name, setIsStepReady]);\n\n if (currentStep !== name) {\n return null;\n }\n\n return children;\n}\n","import {\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport {\n mapValidationStatusesToOutcome,\n VALIDATION_OUTCOME,\n} from '../../shared';\nimport { useValidations } from './useValidations';\nimport { FormContextApi } from '../contexts/FormContext';\n\ninterface FormStatus {\n formStatus : VALIDATION_OUTCOME,\n isFormValid: boolean,\n}\n\n/**\n * Hook to watch form status.\n * @param form (optional) form to use. If it's not given, form context is used.\n */\nexport function useFormStatus(form?: FormContextApi): FormStatus {\n const validations = useValidations(undefined, form);\n const [status, setStatus] = useState(VALIDATION_OUTCOME.UNDETERMINED);\n\n useEffect(() => {\n setStatus(mapValidationStatusesToOutcome(validations));\n }, [validations]);\n\n return useMemo(() => ({\n formStatus: status,\n isFormValid: status === VALIDATION_OUTCOME.VALID,\n }), [status]);\n}\n","import { useMemo } from 'react';\nimport { FieldValue } from '../../shared';\nimport { FormContextApi, useFormContext } from '../contexts/FormContext';\nimport { useOnChangeValues } from './useValues';\n\ninterface UseHiddenField {\n name: string,\n value: FieldValue,\n setValue: (newValue: FieldValue) => void,\n}\n\n/**\n * Hook to use in association to HiddenField\n * @param name Field name\n * @param form (optional) If not specified, context form is used\n * @example\n * ```\n * const fooHiddenField = useHiddenField('foo');\n * return (\n * <HiddenField {...fooHiddenField} />\n * )\n * ```\n */\nexport function useHiddenField(name: string, form?: FormContextApi): UseHiddenField {\n const { setFormValues } = useFormContext();\n const { [name]: value } = useOnChangeValues([name], form);\n\n return useMemo(() => ({\n name,\n value,\n setValue: (newValue) => (\n form\n ? form.setFormValues({ [name]: newValue })\n : setFormValues({ [name]: newValue })\n ),\n }), [name, value, form, setFormValues]);\n}\n","import {\n useCallback,\n useEffect,\n useState,\n} from 'react';\nimport { VALIDATION_OUTCOME } from '../../shared';\nimport { PlaceholderContent } from '../types';\nimport { useWizardContext } from '../contexts/WizardContext';\n\ninterface UsePlaceholder {\n handleOnNext: () => void,\n handleOnPrevious: () => void,\n placeholderContent: PlaceholderContent,\n isLoading: boolean,\n stepStatus: VALIDATION_OUTCOME,\n}\nexport function usePlaceholder(): UsePlaceholder {\n const {\n handleOnNext,\n handleOnPrevious,\n registerPlaceholder,\n unregisterPlaceholder,\n } = useWizardContext();\n\n const [placeholderContent, setPlaceholderContent] = useState<PlaceholderContent>({} as PlaceholderContent);\n const [isLoading, setIsLoading] = useState(false);\n const [\n stepStatus,\n setStepStatus,\n ] = useState<VALIDATION_OUTCOME>(VALIDATION_OUTCOME.VALID);\n\n useEffect(() => {\n registerPlaceholder(setPlaceholderContent, setStepStatus);\n return () => unregisterPlaceholder(setPlaceholderContent, setStepStatus);\n }, [registerPlaceholder, unregisterPlaceholder, setPlaceholderContent, setStepStatus]);\n\n const onNext = useCallback(async () => {\n setIsLoading(true);\n await handleOnNext();\n setIsLoading(false);\n }, [handleOnNext]);\n\n return {\n handleOnNext: onNext,\n handleOnPrevious,\n placeholderContent,\n isLoading,\n stepStatus,\n };\n}\n","import {\n useCallback,\n useEffect,\n useRef,\n} from 'react';\nimport {\n FieldValues,\n mapValidationStatusesToOutcome,\n VALIDATION_OUTCOME,\n ValidationStatus,\n ValidationStatuses,\n} from '../../shared';\nimport {\n FormContextApi,\n PublishSubscriber,\n useForm,\n UseFormOptions,\n} from '../../Form';\nimport { useWizardContext } from '../contexts/WizardContext';\n\ninterface UseStepFormApi {\n form: FormContextApi,\n initFormValues: (initialValues: FieldValues) => void,\n}\n\n/**\n * Using Form in Wizard context. Data will be save and get on step change.\n * @return UseStepFormApi\n * @example\n * ```\n * const {form, initFormValues} = useStepForm()\n *\n * useEffect(() => {\n * // If data is stored in wizard, this will not set data\n * initFormValues({\"foo\": \"foo\", \"bar\": \"bar\"})\n * },[]);\n *\n * return(\n * <Form form={form}>\n * <TextField name=\"foo\" />\n * </Form>\n * )\n * ```\n */\nexport function useStepForm(props: UseFormOptions): UseStepFormApi {\n const form = useForm(props);\n const {\n stepStatusSetter,\n setValuesGetterForCurrentStep,\n getValuesOfCurrentStep,\n } = useWizardContext();\n const valuesHasBeenInitializedRef = useRef(false);\n\n const { formInternal: { addValidationStatusSubscriber }, getFormValues, setFormValues } = form;\n\n useEffect(() => {\n // Form validation is async.\n // So by then, set step status to undefined, the user will not be able to go to next step\n stepStatusSetter(VALIDATION_OUTCOME.UNDETERMINED);\n\n const setFormStatusFromValidationStatuses = ((validationStatuses: ValidationStatuses) => {\n stepStatusSetter(mapValidationStatusesToOutcome(validationStatuses));\n }) as PublishSubscriber<ValidationStatus>;\n /*\n Put function onto the queue. The action will be done only pending render is done\n In case of big step with many inputs, stepStatus will stay UNDETERMINED until that the render\n is done and only after that, the timeout will be run\n */\n setTimeout(() => addValidationStatusSubscriber(setFormStatusFromValidationStatuses), 0);\n\n return () => {\n setValuesGetterForCurrentStep(() => undefined);\n // On step switch, switch stepStatus, user will not be stuck on not clickable button\n stepStatusSetter(VALIDATION_OUTCOME.VALID);\n };\n }, [stepStatusSetter, setValuesGetterForCurrentStep, addValidationStatusSubscriber]);\n\n useEffect(() => {\n setValuesGetterForCurrentStep(getFormValues);\n }, [getFormValues, setValuesGetterForCurrentStep]);\n\n useEffect(() => {\n const valuesOfCurrentStep = getValuesOfCurrentStep();\n // If values for the current step are stored in the wizard context, we update values of the form\n // and set valuesHasBeenInitialized to true to detect if values getting has been done\n if (valuesOfCurrentStep) {\n valuesHasBeenInitializedRef.current = true;\n setFormValues(valuesOfCurrentStep);\n }\n }, [getValuesOfCurrentStep, setFormValues]);\n\n const initFormValues = useCallback((initialValues: FieldValues) : void => {\n // when values from the wizard has been gotten, the function do nothing\n if (!valuesHasBeenInitializedRef.current) {\n setFormValues(initialValues);\n }\n }, [setFormValues]);\n\n return { initFormValues, form };\n}\n","import {\n ReactNode,\n useCallback,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n FieldValues,\n VALIDATION_OUTCO