Skip to content

Commit

Permalink
Fixed half of tests, split out component to props based and HoC for s…
Browse files Browse the repository at this point in the history
…tate
  • Loading branch information
zackify committed Oct 12, 2017
1 parent fda2793 commit 81956e9
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 182 deletions.
101 changes: 101 additions & 0 deletions src/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import Input from './input';
import Submit from './submit';
import Validator from 'validatorjs';
import GetChildren from './get-children';

export default class FormBase extends React.Component {
constructor() {
super();
this.onChange = this.onChange.bind(this);
this.validate = this.validate.bind(this);
this.validateOnBlurOrChange = this.validateOnBlurOrChange.bind(this);
}

validate(onClick) {
let {
rules,
errorMessages = {},
attributeNames = {},
onErrors,
values,
} = this.props;
if (!rules) return onClick(values);

const runner = new Validator(values, rules, errorMessages);
runner.setAttributeNames(attributeNames);

if (runner.fails()) {
return onErrors(runner.errors.errors);
} else onErrors(undefined);

return onClick(values);
}

validateOnBlurOrChange(name, value, onChange) {
if (onChange) onChange();
let {
rules,
errorMessages = {},
attributeNames = {},
errors = {},
onErrors,
} = this.props;

if (!rules || !rules[name]) return;

const runner = new Validator(
{ [name]: value },
{ [name]: rules[name] },
errorMessages
);
runner.setAttributeNames(attributeNames);

if (runner.fails() && value && !onChange) {
return onErrors({ ...errors, ...runner.errors.errors });
}
if (errors[name]) {
return onErrors({ ...errors, [name]: null });
}
}

onChange({ target }) {
let { onValues, values } = this.props;

values[target.name] =
target.type === 'checkbox' || target.type === 'radio'
? target.checked
: target.value;

onValues({ ...values });
}

renderChildren(children) {
return React.Children.map(children, child => {
if (!child || !child.props) return child;

let children = GetChildren(child, this);

let { values = {}, errors } = this.props;
if (child.props.name) return Input(child, values, children, errors, this);
if (child.props.submit) return Submit(child, children, this);

return React.cloneElement(child, { children });
});
}

render() {
let {
children,
rules,
errorMessages,
attributeNames,
values,
onValues,
errors,
onErrors,
...props
} = this.props;
return <div {...props}>{this.renderChildren(children)}</div>;
}
}
109 changes: 22 additions & 87 deletions src/form.js
Original file line number Diff line number Diff line change
@@ -1,96 +1,31 @@
import React from 'react';
import Input from './input';
import Submit from './submit';
import Validator from 'validatorjs';
import GetChildren from './get-children';
import BaseForm from './base';

export default class Form extends React.Component {
constructor({ values = {}, onValues }) {
constructor({ values = {}, errors = {} }) {
super();
this.state = { values: onValues ? {} : values, errors: {} };
this.onChange = this.onChange.bind(this);
this.validate = this.validate.bind(this);
this.validateOnBlurOrChange = this.validateOnBlurOrChange.bind(this);
}

validate(onClick) {
let { rules, errorMessages = {}, attributeNames = {} } = this.props;
let values = { ...this.props.values, ...this.state.values };
if (!rules) return onClick(values);

const runner = new Validator(values, rules, errorMessages);
runner.setAttributeNames(attributeNames);

if (runner.fails()) {
return this.setState({ errors: runner.errors.errors });
} else this.setState({ errors: {} });

return onClick(values);
}

validateOnBlurOrChange(name, onChange) {
if (onChange) onChange();

let { rules, errorMessages = {}, attributeNames = {} } = this.props;
let { errors, values } = this.state;
if (!rules || !rules[name]) return;

const runner = new Validator(
{ [name]: values[name] },
{ [name]: rules[name] },
errorMessages
);
runner.setAttributeNames(attributeNames);

if (runner.fails() && values[name] && !onChange) {
return this.setState({ errors: { ...errors, ...runner.errors.errors } });
}
if (errors[name]) {
return this.setState({ errors: { ...errors, [name]: null } });
}
}

onChange({ target }) {
let { onValues } = this.props;
let values = { ...this.props.values, ...this.state.values };

values[target.name] =
target.type === 'checkbox' || target.type === 'radio'
? target.checked
: target.value;

if (onValues) return onValues({ ...values });

this.setState({ values });
}

renderChildren(children) {
return React.Children.map(children, child => {
if (!child || !child.props) return child;

let children = GetChildren(child, this);

let { values = {} } = this.props;
let { errors } = this.state;

if (child.props.name) return Input(child, values, children, errors, this);
if (child.props.submit) return Submit(child, children, this);

return React.cloneElement(child, { children });
});
this.state = { values, errors };
}

render() {
let {
children,
rules,
errorMessages,
attributeNames,
values,
onValues,
errors,
...props
} = this.props;
return <div {...props}>{this.renderChildren(children)}</div>;
let { values, errors } = this.state;
let { children, ...props } = this.props;
return (
<BaseForm
{...props}
values={values}
errors={errors}
onValues={values => {
//console.log(values, 'values');
this.setState({ values });
}}
onErrors={errors => {
//console.log(errors, 'wtf');
this.setState({ errors });
}}
>
{children}
</BaseForm>
);
}
}
7 changes: 2 additions & 5 deletions src/get-error.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// errors are returned a little weird from the validator so we split it out
export default (errors, propErrors, name) => {
if (propErrors) errors = { ...errors, ...propErrors };

return errors[name] && typeof errors[name] !== 'string'
export default (errors = {}, name) =>
errors[name] && typeof errors[name] !== 'string'
? errors[name][0]
: errors[name] || '';
};
17 changes: 9 additions & 8 deletions src/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,26 @@ const filters = {
text: value => value,
};

const getValue = (type, values) => {
const getValue = (type, rawValue) => {
let filter = filters[type] || filters.text;

let value = filter(values.filter(filter)[0]);
let value = filter(rawValue);
if (value || value === false) return value;

return '';
};

export default (child, propValues, children, errors, component) => {
export default (child, values, children, errors, component) => {
let { name, onKeyUp, onEnter, type } = child.props;

return React.cloneElement(child, {
children,
onChange: e =>
component.validateOnBlurOrChange(name, () => component.onChange(e)),
onBlur: () => component.validateOnBlurOrChange(name),
error: getError(errors, component.props.errors, name),
value: getValue(type, [component.state.values[name], propValues[name]]),
component.validateOnBlurOrChange(name, e.target.value, () =>
component.onChange(e)
),
onBlur: e => component.validateOnBlurOrChange(name, e.target.value),
error: getError(errors, name),
value: getValue(type, values[name]),
onKeyUp: e => {
if (onKeyUp) onKeyUp(e);
if (e.keyCode !== 13) return;
Expand Down
28 changes: 14 additions & 14 deletions tests/checkbox-radio.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
//Test that components work with fields that pass target.checked instead of target.value
import React from 'react';
import Form from '../src/form';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';

const Input = ({ error, ...props }) =>
error ? <p className="error">{error}</p> : <input {...props} />;
const Input = ({ error, ...props }) => (
<div>
{error ? <p className="error">{error}</p> : null}
<input {...props} />
</div>
);

test('Input works with checkbox', () => {
const wrapper = shallow(
const wrapper = mount(
<Form>
<Input name="Awesome" type="checkbox" />
</Form>
);

wrapper
.find(Input)
.simulate('change', {
target: { name: 'Awesome', checked: true, type: 'checkbox' },
});
wrapper.find('input').simulate('change', {
target: { name: 'Awesome', checked: true, type: 'checkbox' },
});

expect(wrapper.find(Input).props().value).toEqual(true);

wrapper
.find(Input)
.simulate('change', {
target: { name: 'Awesome', checked: false, type: 'checkbox' },
});
wrapper.find('input').simulate('change', {
target: { name: 'Awesome', checked: false, type: 'checkbox' },
});

expect(wrapper.find(Input).props().value).toEqual(false);
});
6 changes: 3 additions & 3 deletions tests/children.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//Test that children are rendered and that submit works recursively
import React from 'react';
import Form from '../src/form';
import { shallow } from 'enzyme';
import { mount } from 'enzyme';

test('Children are rendered correctly with submit', () => {
const wrapper = shallow(
const wrapper = mount(
<Form>
<div className="parent">
<div className="sub-parent">
Expand All @@ -19,7 +19,7 @@ test('Children are rendered correctly with submit', () => {
});

test('Children are rendered correctly with null child', () => {
const wrapper = shallow(
const wrapper = mount(
<Form>
<div className="parent">
<div className="sub-parent">
Expand Down
Loading

0 comments on commit 81956e9

Please sign in to comment.