@explita/daily-toolset-components
Version:
A lightweight and versatile collection of TypeScript utility functions and form components, inspired by ShadCN UI, designed for seamless everyday development. Enhance your Node.js, React, and Next.js projects with a well-structured suite of helpers for st
219 lines (218 loc) • 9.41 kB
JavaScript
"use client";
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useForm = useForm;
const daily_toolset_utils_1 = require("@explita/daily-toolset-utils");
const react_1 = require("react");
const validation_1 = require("@explita/daily-toolset-utils/validation");
// 4. Actual implementation
/**
* Creates a form hook for managing form state, validation, and persistence.
*
* @param props - Options for the form hook.
* @param props.schema - The schema for the form, if using `zod`.
* @param props.defaultValues - The default values for the form, if not using `schema`.
* @param props.errors - The initial errors for the form.
* @param props.mode - The mode of the form, either "controlled" or "uncontrolled".
* @param props.errorParser - A function to parse errors.
* @param props.persist - If true, the form will be persisted to local storage.
* @param props.persistKey - The key to use for persisting the form to local storage.
*
* @returns An object with the following properties:
* - `isValidated`: A boolean indicating whether the form is validated.
* - `values`: The current values of the form.
* - `errors`: The current errors of the form.
* - `validateValue`: A function to validate a single value.
* - `setValue`: A function to set the value of a single field.
* - `getValue`: A function to get the value of a single field.
* - `setValues`: A function to set the values of the form.
* - `setErrors`: A function to set the errors of the form.
* - `clearStorage`: A function to clear the form from local storage.
*/
function useForm(props = {}) {
const previousErrorsRef = (0, react_1.useRef)({});
const previousDefaultValuesRef = (0, react_1.useRef)({});
const { schema = undefined, defaultValues = {}, errors = {}, mode = "controlled", errorParser, persist, persistKey, } = props;
const [currentSchema, setCurrentSchema] = (0, react_1.useState)(schema);
const [formValues, setFormValues] = (0, react_1.useState)({});
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) => {
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, daily_toolset_utils_1.debounce)(async (name, inputValue) => {
var _a, _b;
if (!name || !currentSchema || mode === "uncontrolled")
return;
const result = await (0, validation_1.formValidation)(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]);
const saveFormToLocalStorage = (0, react_1.useCallback)((0, daily_toolset_utils_1.debounce)(() => {
if (persist && persistKey) {
const savedValues = localStorage.getItem(persistKey);
localStorage.setItem(persistKey, JSON.stringify({
...JSON.parse(savedValues || "{}"),
...formValues,
}));
}
}, 500), [formValues, persist, persistKey]);
const loadFormFromLocalStorage = (0, react_1.useCallback)(() => {
if (persist && persistKey) {
const savedValues = localStorage.getItem(persistKey);
if (savedValues) {
setFormValues(JSON.parse(savedValues));
}
}
}, [persist, persistKey]);
(0, react_1.useEffect)(() => {
loadFormFromLocalStorage();
}, [loadFormFromLocalStorage]);
(0, react_1.useEffect)(() => {
saveFormToLocalStorage();
}, [formValues, saveFormToLocalStorage]);
async function validateForm() {
if (!currentSchema || mode === "uncontrolled")
return { isValidated: false, formValues };
const result = await (0, validation_1.formValidation)(currentSchema, formValues);
// console.log(result.errorData.errors);
return {
isValidated: result.success,
formValues: result.data,
formErrors: !result.success ? result.errors : undefined,
};
}
(0, react_1.useEffect)(() => {
const handler = (0, daily_toolset_utils_1.debounce)(() => {
validateForm().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(defaultValues);
previousDefaultValuesRef.current = defaultValues; // Update the ref to the new state
if (persist && persistKey) {
const savedValues = localStorage.getItem(persistKey);
localStorage.setItem(persistKey, JSON.stringify({
...JSON.parse(savedValues || "{}"),
...defaultValues,
}));
}
}
}
}, [defaultValues, persist, 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]);
function reset() {
setFormValues({});
setFormErrors({});
if (!persistKey)
return;
localStorage.removeItem(persistKey);
}
const combinedFormValues = (0, react_1.useMemo)(() => ({
...formValues,
...validatedFormValues,
}), [validatedFormValues, formValues]);
function handleSubmit(onValid) {
return async (event) => {
if (event)
event.preventDefault();
if (!currentSchema) {
return onValid(formValues);
}
const validate = await validateForm();
if (!validate.isValidated) {
setIsValidated(false);
setErrors(validate.formErrors);
return;
}
onValid(validate.formValues);
};
}
return {
isValidated,
values: combinedFormValues,
errors: formErrors,
setSchema,
validateValue,
setValue,
getValue: (name) => combinedFormValues[name],
setValues,
setErrors,
reset,
handleSubmit,
};
}