mirror of
https://github.com/Nezumi-2711/react-training.git
synced 2026-09-22 13:38:51 +00:00
171 lines
4.8 KiB
TypeScript
171 lines
4.8 KiB
TypeScript
import { useState, useEffect, useCallback, ChangeEvent } from 'react';
|
|
|
|
// Utils
|
|
import { getPropValues, isObject, isRequired } from '../helpers/utils';
|
|
|
|
// Types
|
|
import { TKeyValue, TValidator } from '../globals/types';
|
|
|
|
// Constants
|
|
import { ERROR, INITIAL_STATE_SCHEMA, VALUE } from '../constants/variables';
|
|
|
|
/**
|
|
* Custom hooks to validate your Form...
|
|
*
|
|
* @param stateSchema model you stateSchema.
|
|
* @param stateValidatorSchema model your validation.
|
|
* @param submitFormCallback function to be execute during form submission.
|
|
* @returns
|
|
*/
|
|
const useForm = (
|
|
stateSchema = {},
|
|
stateValidatorSchema = {} as TValidator,
|
|
submitFormCallback: (values: TKeyValue) => void,
|
|
initialValue: string = '',
|
|
) => {
|
|
const [values, setValues] = useState<TKeyValue>(INITIAL_STATE_SCHEMA);
|
|
const [errors, setErrors] = useState(getPropValues(stateSchema, ERROR));
|
|
const [valid, isValid] = useState(getPropValues(stateSchema));
|
|
const [disable, setDisable] = useState(true);
|
|
const [isDirty, setIsDirty] = useState(false);
|
|
|
|
// Get a local copy of stateSchema
|
|
useEffect(() => {
|
|
setInitialErrorState(initialValue);
|
|
setDisable(true);
|
|
isValid(getPropValues(stateSchema));
|
|
setValues(getPropValues(stateSchema, VALUE));
|
|
|
|
// If initial value true, setValues again from stateSchema
|
|
// and enabled button
|
|
if (initialValue) {
|
|
setValues(getPropValues(stateSchema, VALUE));
|
|
setDisable(false);
|
|
}
|
|
}, [initialValue]); // eslint-disable-line
|
|
|
|
// Validate fields in forms
|
|
const validateFormFields = useCallback(
|
|
(name: string, value: string) => {
|
|
const validator = stateValidatorSchema;
|
|
// Making sure that stateValidatorSchema name is same in
|
|
// stateSchema
|
|
if (!validator[name]) return;
|
|
|
|
const field = validator[name];
|
|
|
|
let error = '';
|
|
|
|
// Skip check id field
|
|
if (name !== 'id' && name !== 'roomId') {
|
|
error = isRequired(value, field!.required);
|
|
|
|
if (isObject(field['validator']) && error === '') {
|
|
const fieldValidator = field['validator'];
|
|
|
|
// Test the function callback if the value is meet the criteria
|
|
const testFunc = fieldValidator!['func'];
|
|
if (!testFunc!(value)) {
|
|
error = fieldValidator!['error']!;
|
|
}
|
|
}
|
|
}
|
|
|
|
return error;
|
|
},
|
|
[stateValidatorSchema],
|
|
);
|
|
|
|
// Set Initial Error State
|
|
// When hooks was first rendered...
|
|
const setInitialErrorState = useCallback(
|
|
(initialValue: string) => {
|
|
Object.keys(errors).map((name) =>
|
|
setErrors((prevState) => ({
|
|
...prevState,
|
|
[name]: !initialValue // Skip error when initialValue have values
|
|
? validateFormFields(name, values[name] as string)
|
|
: '',
|
|
})),
|
|
);
|
|
},
|
|
[errors, values, validateFormFields],
|
|
);
|
|
|
|
// Used to disable submit button if there's a value in errors
|
|
// or the required field in state has no value.
|
|
// Wrapped in useCallback to cached the function to avoid intensive memory leaked
|
|
// in every re-render in component
|
|
const validateErrorState = useCallback(
|
|
() => Object.values(errors).some((error) => error),
|
|
[errors],
|
|
);
|
|
|
|
// For every changed in our state this will be fired
|
|
// To be able to disable the button
|
|
useEffect(() => {
|
|
if (isDirty) {
|
|
setDisable(validateErrorState());
|
|
}
|
|
}, [errors, isDirty, validateErrorState]);
|
|
|
|
// Event handler for handling changes in input.
|
|
const handleOnChange = useCallback(
|
|
(
|
|
event: ChangeEvent<
|
|
HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement
|
|
>,
|
|
) => {
|
|
setIsDirty(true);
|
|
|
|
let error = '';
|
|
const name = (event.target! as HTMLInputElement).name;
|
|
let value: string | boolean = '';
|
|
|
|
if ((event.target! as HTMLInputElement).type === 'checkbox') {
|
|
value = (event.target! as HTMLInputElement).checked;
|
|
} else {
|
|
value = (event.target! as HTMLInputElement).value;
|
|
}
|
|
|
|
if (typeof value === 'string') {
|
|
error = validateFormFields(name, value)!;
|
|
}
|
|
|
|
setValues((prevState) => ({ ...prevState, [name]: value }));
|
|
setErrors((prevState) => ({ ...prevState, [name]: error }));
|
|
isValid((prevState) => ({ ...prevState, [name]: true }));
|
|
},
|
|
[validateFormFields],
|
|
);
|
|
|
|
const handleOnSubmit = useCallback(
|
|
(event: React.FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
|
|
// Making sure that there's no error in the state
|
|
// before calling the submit callback function
|
|
// and disabled button
|
|
|
|
if (!validateErrorState()) {
|
|
submitFormCallback(values);
|
|
setDisable(true);
|
|
}
|
|
},
|
|
[validateErrorState, submitFormCallback, values],
|
|
);
|
|
|
|
return {
|
|
handleOnChange,
|
|
handleOnSubmit,
|
|
values,
|
|
errors,
|
|
disable,
|
|
setValues,
|
|
setErrors,
|
|
valid,
|
|
};
|
|
};
|
|
|
|
export default useForm;
|