pyro-form
Version:
Pyro-Form is a fast and simple form manager for react that helps you with managing your components data in a form.
159 lines (151 loc) • 6.2 kB
JavaScript
import React, { useState, useEffect, useContext } from 'react';
const getValueFromEvent = (event) => event.currentTarget.value;
const isEvent = (event) => event && event.target && typeof event.target.value === 'string';
const isPromise = (value) => Boolean(value) && typeof value.then === 'function';
// tslint:disable-next-line:ban-types
const isFunction = (value) => typeof value === 'function';
const PyroContext = React.createContext(undefined);
const getPyroConsumer = () => PyroContext.Consumer;
const PyroProvider = PyroContext.Provider;
function createValues(values, value) {
return Object.keys(values).reduce((previousValue, currentValue) => (Object.assign(Object.assign({}, previousValue), { [currentValue]: value })), {});
}
const PyroForm2 = ({ initialValues, onValidate: handleValidate, onSubmit: handleSubmit, children, }) => {
const [isMounted, setIsMounted] = useState(true);
const [values, setValues] = useState(initialValues);
const [touched, setTouched] = useState(createValues(initialValues, false));
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [openValidations, setOpenValidations] = useState(0);
const [submitCount, setSubmitCount] = useState(0);
const onValidate = React.useCallback(async () => {
if (!handleValidate)
return;
const validateResult = handleValidate(values);
if (isPromise(validateResult)) {
setOpenValidations(openValidations + 1);
await validateResult;
setOpenValidations(openValidations - 1);
}
// TODO: Just do if mounted
setErrors(validateResult);
}, []);
const touchValue = React.useCallback((name) => {
setTouched(oldTouched => (Object.assign(Object.assign({}, oldTouched), { [name]: true })));
}, []);
const onSubmit = async (e) => {
if (e) {
e.preventDefault();
e.stopPropagation();
}
// Touch all values
setTouched(createValues(initialValues, true));
// Execute submit action if defined
if (handleSubmit) {
const submitResult = handleSubmit(values);
// Await async submits
if (isPromise(submitResult)) {
setIsSubmitting(true);
await submitResult;
}
}
// TODO: Just do if mounted
// Indicate a finished submission
setIsSubmitting(false);
setSubmitCount(submitCount + 1);
};
const onChange = React.useCallback((name, value) => {
// Check if passed value is an event and use it's value
const sanitizedValue = isEvent(value) ? getValueFromEvent(value) : value;
// Set updated value
setValues(oldValues => (Object.assign(Object.assign({}, oldValues), { [name]: sanitizedValue })));
}, []);
const onBlur = React.useCallback((name) => {
touchValue(name);
}, []);
// Trigger validation every time values change
useEffect(() => {
// TODO: Use low scheduler priority
onValidate();
}, [values]);
const contextValue = {
errors,
touched,
values,
handleBlur: onBlur,
handleChange: onChange,
handleSubmit: onSubmit,
};
return (React.createElement(PyroProvider, { value: contextValue }, isFunction(children)
? children(Object.assign(Object.assign({}, contextValue), { hasErrors: Object.keys(errors).length !== 0 }))
: children));
};
const usePyroField = (name) => {
const context = useContext(PyroContext);
if (!context) {
// tslint:disable-next-line:no-console
throw new Error('usePyroField hook not used in a valid PyroContext');
}
return {
core: {
name,
value: context.values[name],
onChange: (value) => context.handleChange(name, value),
onBlur: () => context.handleBlur(name),
},
meta: {
error: context.errors[name],
hasError: Boolean(context.errors[name]),
touched: context.touched[name],
},
};
};
const Form = props => {
const context = React.useContext(PyroContext);
if (!context) {
// tslint:disable-next-line:no-console
console.error('Form used in non PyroForm context');
return null;
}
return React.createElement("form", Object.assign({ onSubmit: context.handleSubmit }, props));
};
class PyroFieldInner extends React.PureComponent {
constructor() {
super(...arguments);
this.handleChange = (value) => {
this.props.handleChange && this.props.handleChange(this.props.name, value);
};
this.handleBlur = () => {
this.props.handleBlur && this.props.handleBlur(this.props.name);
};
}
render() {
if (!this.props.values || !this.props.errors || !this.props.touched) {
throw new Error('Please use PyroField Components only within a PyroForm');
}
return this.props.children({
core: {
name: this.props.name,
value: this.props.values[this.props.name],
onChange: this.handleChange,
onBlur: this.handleBlur,
},
meta: {
error: this.props.errors[this.props.name],
hasError: Boolean(this.props.errors[this.props.name]),
touched: this.props.touched[this.props.name],
},
});
}
}
class PyroField extends React.PureComponent {
constructor() {
super(...arguments);
this.PyroConsumer = getPyroConsumer();
}
render() {
return (React.createElement(this.PyroConsumer, null, contextProps => (React.createElement(PyroFieldInner, Object.assign({}, this.props, contextProps)))));
}
}
export default PyroForm2;
export { Form, PyroContext, PyroField, PyroProvider, getPyroConsumer, getValueFromEvent, isEvent, isFunction, isPromise, usePyroField };