@explita/daily-toolset-form
Version:
A lightweight form toolkit for React built with developer ergonomics in mind. Includes a flexible Form component, useForm, useField, and useFormContext hooks for managing form state and validation with ease. Designed to simplify complex forms while remain
253 lines (252 loc) • 9.75 kB
JavaScript
"use client";
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useForm = useForm;
const react_1 = require("react");
const utils_1 = require("../utils");
const utils_2 = require("../lib/utils");
function useForm(props = {}
//@ts-ignore
) {
const previousErrorsRef = (0, react_1.useRef)({});
const previousDefaultValuesRef = (0, react_1.useRef)({});
const { schema, defaultValues = {}, errors = {}, mode = "controlled", resetOnSuccess = false, errorParser,
// persist,
persistKey, onSubmit, onSuccess, onError, } = props;
const [currentSchema, setCurrentSchema] = (0, react_1.useState)(schema);
const generatePlaceholders = (0, react_1.useMemo)(() => {
if (mode === "controlled") {
return {
...(0, utils_2.createEmptyValues)(currentSchema),
...defaultValues,
};
}
return {};
}, [mode, currentSchema, defaultValues]);
const [formValues, setFormValues] = (0, react_1.useState)(generatePlaceholders);
const [formErrors, setFormErrors] = (0, react_1.useState)({});
const [isValidated, setIsValidated] = (0, react_1.useState)(false);
const [validatedFormValues, setValidatedFormValues] = (0, react_1.useState)(defaultValues);
const setSchema = (0, react_1.useCallback)((newSchema) => {
setCurrentSchema(newSchema);
}, [setCurrentSchema]);
(0, react_1.useEffect)(() => {
setCurrentSchema(schema);
}, [schema]);
const parseFormErrors = (0, react_1.useCallback)((errors) => {
// @ts-ignore
setFormErrors((prev) => {
const newErrors = { ...errors };
if (errorParser && typeof errorParser === "function") {
return Object.keys(newErrors).reduce((acc, key) => {
var _a, _b;
acc[key] = (_b = errorParser((_a = newErrors[key]) !== null && _a !== void 0 ? _a : "")) !== null && _b !== void 0 ? _b : "";
return acc;
}, {});
}
return newErrors;
});
}, [errorParser]);
const setValue = (0, react_1.useCallback)((name, value) => {
if (mode === "uncontrolled" || !name)
return;
setFormValues((prev) => ({
...prev,
[name]: value,
}));
}, [mode]);
const setValues = (0, react_1.useCallback)((values, options) => {
setFormValues((prev) => ({
...((options === null || options === void 0 ? void 0 : options.overwrite) ? {} : prev),
...values,
}));
}, []);
const setErrors = (0, react_1.useCallback)((errors) => {
parseFormErrors(errors);
}, [parseFormErrors]);
const validateValue = (0, react_1.useCallback)((0, utils_1.debounce)(async (name, inputValue) => {
var _a, _b;
if (!name || !currentSchema || mode === "uncontrolled")
return;
const result = await (0, utils_1.validateForm)(currentSchema, {
[name]: (_a = inputValue === null || inputValue === void 0 ? void 0 : inputValue.toString()) !== null && _a !== void 0 ? _a : "",
});
if (!result.success) {
const fieldError = (_b = result.errors[name]) !== null && _b !== void 0 ? _b : "";
setFormErrors((prev) => ({
...prev,
[name]: errorParser ? errorParser(fieldError) : fieldError,
}));
}
else {
setFormErrors((prev) => ({ ...prev, [name]: undefined }));
}
}, 200), [currentSchema, mode, errorParser]);
(0, react_1.useEffect)(() => {
if (persistKey) {
const savedValues = (0, utils_1.readDraft)(persistKey);
if (savedValues) {
setFormValues({ ...generatePlaceholders, ...savedValues });
}
}
}, [persistKey]);
(0, react_1.useEffect)(() => {
if (persistKey) {
const savedValues = (0, utils_1.readDraft)(persistKey);
(0, utils_1.writeDraft)({
...(savedValues || {}),
...formValues,
}, persistKey);
}
}, [formValues]);
async function formValidation() {
if (!currentSchema || mode === "uncontrolled")
return { isValidated: false, formValues };
const result = await (0, utils_1.validateForm)(currentSchema, formValues);
return {
isValidated: result.success,
formValues: result.data,
formErrors: !result.success ? result.errors : undefined,
};
}
(0, react_1.useEffect)(() => {
const handler = (0, utils_1.debounce)(() => {
formValidation().then(({ isValidated, formValues }) => {
setIsValidated(isValidated);
setValidatedFormValues(formValues);
});
}, 200);
handler();
return () => handler.cancel();
}, [formValues, currentSchema, mode]);
(0, react_1.useEffect)(() => {
if (defaultValues) {
const previousDefaultValues = previousDefaultValuesRef.current;
const valuesEqual = Object.keys(previousDefaultValues).length ===
Object.keys(defaultValues).length &&
Object.keys(defaultValues).every((key) => previousDefaultValues[key] === defaultValues[key]);
if (!valuesEqual) {
setFormValues(generatePlaceholders);
previousDefaultValuesRef.current = defaultValues; // Update the ref to the new state
if (persistKey) {
const savedValues = (0, utils_1.readDraft)(persistKey);
(0, utils_1.writeDraft)({
...(savedValues || {}),
...defaultValues,
}, persistKey);
}
}
}
}, [defaultValues, persistKey]);
(0, react_1.useEffect)(() => {
if (errors) {
if (Object.keys(errors).length === 0) {
// Only clear errors if they are different from the previous state
if (Object.keys(previousErrorsRef.current).length > 0) {
setFormErrors({});
previousErrorsRef.current = {}; // Update the ref to the new state
}
}
else {
const hasErrorsChanged = Object.keys(errors).length !==
Object.keys(previousErrorsRef.current).length ||
Object.keys(errors).some((key) => previousErrorsRef.current[key] !== errors[key]);
if (hasErrorsChanged) {
parseFormErrors(errors);
previousErrorsRef.current = errors; // Update the ref to the new state
}
}
}
}, [errors]);
const combinedFormValues = (0, react_1.useMemo)(() => ({
...formValues,
...validatedFormValues,
}), [validatedFormValues, formValues]);
function register(name) {
return {
name,
id: name === null || name === void 0 ? void 0 : name.toString(),
defaultValue: name ? combinedFormValues[name] : undefined,
onChange: (e) => {
setValue(name, e.currentTarget.value);
validateValue(name, e.currentTarget.value);
},
onInput: (e) => {
setValue(name, e.currentTarget.value);
validateValue(name, e.currentTarget.value);
},
onBlur: () => {
// future: setTouched(name);
},
};
}
function reset() {
setFormValues(generatePlaceholders);
setFormErrors({});
if (persistKey)
(0, utils_1.deleteDraft)(persistKey);
}
function handleSubmit(onValid) {
return async (event) => {
if (event)
event.preventDefault();
if (!currentSchema) {
return onValid(formValues);
}
const validate = await formValidation();
if (!validate.isValidated) {
setIsValidated(false);
setErrors(validate.formErrors);
return;
}
onValid(validate.formValues);
};
}
const [state, formAction, isSubmitting] = (0, react_1.useActionState)(async (prev, formData) => {
if (currentSchema) {
const validate = await formValidation();
if (!validate.isValidated) {
setIsValidated(false);
setErrors(validate.formErrors);
return;
}
}
if (!onSubmit)
return;
try {
const res = await onSubmit(combinedFormValues, formData);
onSuccess === null || onSuccess === void 0 ? void 0 : onSuccess(res, {
reset,
values: combinedFormValues,
formData,
});
setErrors({});
if (resetOnSuccess)
reset();
return res;
}
catch (error) {
onError === null || onError === void 0 ? void 0 : onError(error, { setErrors, setValues });
return error;
}
}, undefined);
return {
isValidated,
values: combinedFormValues,
errors: formErrors,
setSchema,
validateValue,
setValue,
getValue: (name) => combinedFormValues[name],
setValues,
setErrors,
reset,
handleSubmit,
register,
// @ts-ignore
errorParser,
formAction: onSubmit ? formAction : undefined,
isSubmitting,
state: state,
};
}