Skip to content

Instantly share code, notes, and snippets.

@jordangarcia
Created August 6, 2018 22:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jordangarcia/fbeee82420ebf2b2f9c1c56a597d67d0 to your computer and use it in GitHub Desktop.
Save jordangarcia/fbeee82420ebf2b2f9c1c56a597d67d0 to your computer and use it in GitHub Desktop.
import _ from 'lodash';
import React from 'react';
import ui from 'core/ui';
import PropTypes from 'prop-types';
import { Immutable } from 'nuclear-js';
import { connect } from 'core/ui/decorators';
function mergeErrors(orig, newError) {
const hasError = orig.hasError || newError.hasError;
const details = _.merge(orig.details, newError.details);
return {
hasError,
details,
}
}
export function Form({
validateOnChange = true,
initialStateGetter,
// TODO(jordan): add _.pluck or _.omit functionality here (jess says whitelist is better)
}) {
return function(Component) {
class FormComponent extends React.Component {
constructor(props) {
// TODO(jordan): handle passing ref
super(props);
this.state = {
editing: this.props.clean,
isDirty: false,
errors: {},
isFormValid: true,
}
this.savedRefs = []
this.validationFns = [];
this.form = {
getValue: this.getValue,
setValue: this.setValue,
//update: this.update,
//setFormValues: this.setFormValues,
getErrors: this.getErrors,
revert: this.revert,
addValidationFn: this.addValidationFn,
validateAll: this.validateAll,
field: this.createFieldWrapper,
isFormValid: this.isFormValid,
};
}
componentDidMount() {
this.validateAll();
}
isFormValid = () => {
return this.state.isFormValid;
}
createFieldWrapper = (field) => {
return {
getValue: this.getValue.bind(this, field),
setValue: (val) => {
this.update(field, val);
return this;
},
getErrors: this.getErrors.bind(this, field),
validate: () => {
this.validate(field)
return this;
},
addValidationFn: (entry) => {
this.addValidationFn({
...entry,
keypath: field,
});
return this;
},
}
}
setValue = (...args) => {
let editing;
if (args.length === 1) {
this.updateAll(args[0]);
} else if (args.length === 2) {
this.updateAll(args[0], args[1]);
}
}
revert = () => {
this.setState({
editing: this.props.clean,
isDirty: false,
}, () => {
// name
// { hasError: true, details: { msg: 'invalid' }}
// { hasError: true, details: { msg: 'msg 2' }}
// { hasError: true, details: { otherMsg: 'mesgg', }}
// { hasError: true, details: { msg: 'msg 2' }}
this.validateAll();
});
}
getValue = (keypath) => {
if (!keypath) {
return this.state.editing;
}
return this.state.editing.get(keypath);
}
getErrors = (keypath) => {
// TODO(jordan): what does a "form generic error" look like
return this.state.errors[keypath] || {
hasErrors: false,
details: {},
};
}
addValidationFn = (entry) => {
this.validationFns.push(entry);
return function remove() {
this.validationFns.splice(this.validationFns.indexOf(entry), 1);
}.bind(this)
}
validate = (keypathToCheck) => {
let errors = _.cloneDeep(this.state.errors);
delete errors[keypathToCheck];
this.validationFns
.filter(({ keypath }) => keypathToCheck === keypath)
.forEach(({ keypath, getErrors }) => {
const existingError = errors[keypath];
let error = getErrors(this.getValue(keypath), this.state.editing)
if (existingError) {
error = mergeErrors(existingError, error);
}
console.log('adding error', keypath, error)
// TODO(jordan): merge of error objects if one exists
errors[keypath] = error;
});
const isFormValid = _.every(errors, ({ hasErrors }) => !hasErrors);
console.log('validating keypath', keypathToCheck, isFormValid)
this.setState({
isFormValid,
errors,
});
}
getValidateAllResult = () => {
let errors = {}
let isFormValid = true;
this.validationFns.forEach(({ keypath, getErrors }) => {
const existingError = errors[keypath];
let error = getErrors(this.getValue(keypath), this.state.editing)
if (existingError) {
error = mergeErrors(existingError, error);
}
// TODO(jordan): merge of error objects if one exists
errors[keypath] = error;
if (error.hasError) {
isFormValid = false;
}
});
return {
isFormValid,
errors,
}
}
validateAll = () => {
const res = this.getValidateAllResult();
this.setState(res);
}
updateIsDirty = (editing, clean) => {
const isDirty = !Immutable.is(editing, clean);
this.setState({
isDirty,
})
}
updateAll = (formValues) => {
this.setState((prevState, props) => {
const editing = formValues;
const isDirty = !Immutable.is(editing, props.clean);
return {
editing,
isDirty,
};
}, () => {
if (validateOnChange) {
this.validateAll();
}
});
}
update = (keypath, value) => {
this.setState((prevState, props) => {
const editing = prevState.editing.set(keypath, value);
const isDirty = !Immutable.is(editing, props.clean);
return {
editing,
isDirty,
};
}, () => {
if (validateOnChange) {
this.validate(keypath);
}
});
}
render() {
const {
form,
} = this.props;
return (
<Component
{ ...this.props }
form={ this.form }
formState={ this.state }
/>
)
}
}
FormComponent.componentId = Component.componentId;
return ui.connectGetters(
FormComponent,
(props) => {
const clean = _.isFunction(initialStateGetter)
? initialStateGetter(props)
: initialStateGetter;
return {
clean,
}
}
)
}
}
export default Form
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment