@shopgate/engage
Version:
Shopgate's ENGAGE library.
162 lines (152 loc) • 4.87 kB
JavaScript
import _debounce from "lodash/debounce";
import { useRef, useState, useEffect, useCallback } from 'react';
import { useScrollTo } from "./useScrollTo";
import { i18n } from "../helpers/i18n";
import { useValidation } from "../validation";
/**
* Converts validation errors into errors for form builder.
* @param {Object} validationErrors The validation errors.
* @returns {Array}
*/
export const convertValidationErrors = validationErrors => Object.keys(validationErrors).map(key => ({
path: key,
message: i18n.text(validationErrors[key])
}));
/**
* @param {Object} initialState The initial form state.
* @param {Function} complete The completion callback.
* @param {Object} validationConstraints validationConstraints
* @param {Object} [formContainerRef=null] A ref to a container with forms
* @param {number} [validationErrorScrollOffset=10] When a form container ref is passed with the
* parameters, whenever validation errors occur, the page will scroll to the first error. Depending
* on the page, the scroll logic might not be accurate. So this offset parameter can be used
* to influence scroll behavior.
* @returns {{ handleChange, handleSubmit, values, valid, validationErrors: ?Object, isSubmitting }}
*/
export function useFormState(initialState, complete, validationConstraints = {}, formContainerRef = null, validationErrorScrollOffset = 10) {
const {
scrollTo
} = useScrollTo(formContainerRef, validationErrorScrollOffset);
// Submit lock prevents the form from being submitted multiple times
const submitLock = useRef(false);
const [values, setValues] = useState(initialState);
const [isSubmitting, setSubmitting] = useState(false);
const [changed, setChanged] = useState(false);
const [ignoreErrors, setIgnoreErrors] = useState(false);
const {
valid,
validationErrors,
validate,
reset
} = useValidation(validationConstraints);
const scrollToError = useCallback(() => {
const errorElement = document.querySelector('.validationError');
if (errorElement) {
scrollTo('.validationError');
const inputElement = errorElement.querySelector('input, select, textarea');
if (inputElement) {
inputElement.focus();
}
}
}, [scrollTo]);
// -- CHANGED ---------------
useEffect(() => {
const isEqual = JSON.stringify(values) === JSON.stringify(initialState);
if (!isEqual && !changed) {
setChanged(true);
} else if (isEqual && changed) {
setChanged(false);
}
}, [changed, initialState, values]);
// -- IS_SUBMITTING ---------
/* eslint-disable react-hooks/exhaustive-deps */
useEffect(() => {
if (!isSubmitting) {
return;
}
let mounted = true;
if ((valid === true || ignoreErrors) && !submitLock.current) {
submitLock.current = true;
(async () => {
await complete(values);
if (mounted) {
setSubmitting(false);
setChanged(false);
submitLock.current = false;
}
})();
}
// eslint-disable-next-line consistent-return
return () => {
mounted = false;
};
}, [isSubmitting, valid, ignoreErrors]);
/* eslint-enable react-hooks/exhaustive-deps */
useEffect(() => {
scrollToError();
}, [isSubmitting, scrollToError]);
useEffect(() => () => {
submitLock.current = false;
}, []);
// -- VALIDATION ON SUBMIT ---------
useEffect(() => {
if (ignoreErrors) {
return;
}
// Yest no validation on submit
if (changed && valid !== null) {
validate(values);
}
if (isSubmitting && valid === null) {
validate(values);
}
if (isSubmitting && valid === false) {
setSubmitting(false);
}
}, [changed, validate, values, isSubmitting, valid, ignoreErrors]);
const handleSetIgnoreErrors = useCallback(value => {
setIgnoreErrors(value);
if (ignoreErrors === true) {
reset();
}
}, [ignoreErrors, reset]);
/**
* @param {string} sanitized The sanitized field value.
* @param {Object} event The change event object.
*/
const handleChange = useCallback((sanitized, event) => {
if (!event) {
return;
}
setValues({
...values,
[event.target.name]: sanitized
});
}, [values]);
/**
* Debounced (dbl-click) submit handler
* @param {Object} event The submit event object.
*/
const handleSubmit = _debounce(event => {
if (event?.preventDefault) {
event.preventDefault();
}
setSubmitting(true);
}, 300, {
leading: true,
trailing: false
});
return {
resetValidationErrors: reset,
handleChange,
handleSubmit,
values,
valid: !ignoreErrors ? valid : true,
validationErrors: !ignoreErrors ? validationErrors : [],
isSubmitting,
setValues,
scrollToError,
setIgnoreErrors: handleSetIgnoreErrors,
validate
};
}