@meronex/form
Version:
A simple react form for React Material UI (and potential other)
231 lines (207 loc) • 5.6 kB
JavaScript
import React from "react";
export class FormClass {
constructor(config, onUpdateFun, blurDelay) {
this.onUpdateFunc = onUpdateFun;
this.blurDelay = blurDelay;
this.init(config);
this.isFormValid();
}
init(config) {
this.config = config;
this.data = {
config,
values: {},
state: {},
errors: {},
};
this.enteries = Object.entries(config);
const values = {};
const state = {};
const errors = {};
for (const [key, fieldConfig] of this.enteries) {
if (fieldConfig.defaultValue) {
values[key] = fieldConfig.defaultValue;
} else {
values[key] = undefined;
}
state[key] = undefined;
errors[key] = undefined;
}
this.data.state = state;
this.data.values = values;
this.data.errors = errors;
this.data.state.formValid = undefined;
}
isFormValid() {
let isValid;
let validCount = 0;
Object.values(this.data.errors).forEach((v) => {
if (typeof v === "string") {
isValid = false;
} else if (v === true) {
validCount += 1;
}
});
if (validCount === this.config.validatorsCount) {
isValid = true;
}
return isValid;
}
isFieldValid(f) {
return typeof this.data.errors[f] === "string";
}
onBlur(f) {
this.blurTimeout = setTimeout(() => {
this.onChange(f, this.data.values[f]);
}, this.blurDelay);
}
onChange(f, v) {
if (f) {
this.data.values[f] = v;
this.validate(f);
}
if (this.onUpdateFunc) {
this.onUpdateFunc({ ...this.data });
}
}
getValue(f) {
return this.data.values[f];
}
getError(f) {
return this.data.errors[f];
}
getData() {
return { ...this.data };
}
validate(f) {
const fieldConfig = this.config[f];
let error = false,
state = {};
if (fieldConfig && typeof fieldConfig.validate === "function") {
error = fieldConfig.validate(this.data.values[f]);
state = Boolean(error);
}
this.data.errors[f] = error;
this.data.state[f] = state;
const _isFormValid = this.isFormValid();
this.data.state.formValid = _isFormValid;
this.data.isValid = _isFormValid;
return this.getData();
}
reset(fireOnChange = true) {
this.init(this.config);
window.clearTimeout(this.blurTimeout);
if (fireOnChange) {
this.onChange();
}
}
static init(config, cb) {
const newForm = new Form(config, cb);
return React.useRef(newForm).current;
}
}
export const FormField = (props) => {
const { form, name } = props;
return React.createElement(
React.Fragment,
null,
React.cloneElement(props.children, {
value: form.getData().values[name],
onChange: (e) => {
form.onChange(name, e.target.value);
},
onBlur: () => form.onBlur(name),
helperText: form.getError(name),
error: form.getError(name),
})
);
};
export const Form = React.forwardRef((props, ref) => {
const { onUpdate, blurDelay = 0, validateOnInit = false } = props;
const [initialized, setInitialized] = React.useState(false);
const form = React.useRef(new FormClass({}, onUpdate, blurDelay)).current;
if (typeof onUpdate === "function") {
form.onUpdateFunc = onUpdate;
}
React.useEffect(() => {
const valuesArray = Object.entries(form.data.values);
if (validateOnInit) {
valuesArray.forEach((v) => {
form.validate(v[0]);
});
form.onChange();
}
setInitialized(true);
return () => form.reset(false);
}, []);
React.useImperativeHandle(ref, () => ({
reset: () => {
form.reset();
},
validate: () => {
form.validate();
},
}));
let children = React.Children.toArray(props.children);
return React.createElement(
React.Fragment,
null,
children.map((c, i) => {
if (!React.isValidElement(c)) {
return;
}
let name, validate, defaultValue, value;
if (c.props) {
name = c.props.name;
validate = c.props.validate;
defaultValue = c.props.defaultValue;
value = c.props.value;
}
if (!form.config.validatorsCount) {
form.config.validatorsCount = 0;
}
if (name) {
if (!form.config[name]) {
form.config[name] = {};
}
if (!initialized) {
if (typeof validate === "function") {
form.config[name].validate = c.props.validate;
form.config.validatorsCount += 1;
}
if (defaultValue) {
form.data.values[name] = defaultValue;
form.config[name].defaultValue = defaultValue;
} else if (value) {
form.data.values[name] = value;
form.config[name].defaultValue = undefined;
} else {
form.data.values[name] = undefined;
form.config[name].defaultValue = undefined;
}
}
}
let childProps =
typeof name !== "undefined"
? {
key: i,
validate: undefined,
defaultValue,
value,
onChange: (e) => {
form.onChange(name, e.target.value);
},
onBlur: () => form.onBlur(name),
helperText: React.createElement(
"span",
null,
form.getError(name)
),
error: form.isFieldValid(name),
}
: { key: i };
return React.cloneElement(c, childProps);
})
);
});
export default Form;