UNPKG

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
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 };