Skip to content
This repository was archived by the owner on May 12, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
75631b6
Refactored mapPropsToChild to accept a mapping of child types & props…
clocasto Feb 26, 2017
b55a571
Modified Field and Form components to adhere to new API for mapPropsT…
clocasto Feb 26, 2017
d76e3f1
Bugfixed updated mapPropsToChild (it wasn't properly determining chil…
clocasto Feb 26, 2017
05dbc5d
Implemented a makePropsForStatus function used to pass the appropriat…
clocasto Feb 26, 2017
6c7a300
Modified initial form validity to run validators, rather than default…
clocasto Feb 26, 2017
65211bc
Updated tests to reflect new initial form validity checking
clocasto Feb 26, 2017
dc53ec6
Updated Form test specs to cover a missed branch in addFieldToState f…
clocasto Feb 26, 2017
0330657
Bugfixed case where mapPropsToChild was erroring due to text node chi…
clocasto Feb 26, 2017
619fc11
Refactored mapPropsToChild to see valid/pristine flag components nest…
clocasto Feb 26, 2017
ce7f468
Re-enabled all tests and re-transpiled src
clocasto Feb 26, 2017
1abbd15
Updated Field component to also watch for 'valid' & 'pristine' tagged…
clocasto Feb 26, 2017
7d14b82
Updated readme.md to reflect changes in this branch
clocasto Feb 26, 2017
038ec57
Reverting readme to previous state due to c/p error from github's editor
clocasto Feb 26, 2017
501a249
Updating the readme to reflect the changes in this branch
clocasto Feb 26, 2017
52825ad
Merge branch 'release-1.1.0' into feature-#21
clocasto Feb 26, 2017
6415312
Updated feature-#30 branch test spec to reflect new initial field sta…
clocasto Feb 26, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions dist/components/Field.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ var _utilities = require('../helpers/utilities');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
Expand Down Expand Up @@ -118,6 +120,8 @@ var Field = function (_React$Component) {
}, {
key: 'render',
value: function render() {
var _this2 = this;

var childCount = _react2.default.Children.count(this.props.children);
var inputProps = {
name: this.props.name,
Expand All @@ -143,8 +147,16 @@ var Field = function (_React$Component) {
'div',
null,
_react2.default.Children.map(this.props.children, function (child) {
return (0, _utilities.mapPropsToChild)(child, 'input', function () {
return inputProps;
return (0, _utilities.mapPropsToChild)(child, {
input: function input() {
return inputProps;
},
valid: function valid() {
return (0, _utilities.makePropsForStatus)('valid', _defineProperty({}, _this2.props.name, { valid: _this2.state.valid }));
},
pristine: function pristine() {
return (0, _utilities.makePropsForStatus)('pristine', _defineProperty({}, _this2.props.name, { pristine: _this2.state.pristine }));
}
});
})
);
Expand Down
11 changes: 9 additions & 2 deletions dist/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,15 @@ var Form = function (_React$Component) {
'form',
{ onSubmit: this.onSubmit },
_react2.default.Children.map(this.props.children, function (child) {
return (0, _utilities.mapPropsToChild)(child, 'Field', function (grandChild) {
return (0, _utilities.makeFieldProps)(grandChild, _this3.onFieldChange, _this3.state);
return (0, _utilities.mapPropsToChild)(child, { Field: function Field(grandChild) {
return (0, _utilities.makeFieldProps)(grandChild, _this3.onFieldChange, _this3.state);
},
pristine: function pristine() {
return (0, _utilities.makePropsForStatus)('pristine', _this3.state);
},
valid: function valid() {
return (0, _utilities.makePropsForStatus)('valid', _this3.state);
}
});
})
);
Expand Down
77 changes: 53 additions & 24 deletions dist/helpers/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
Object.defineProperty(exports, "__esModule", {
value: true
});

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

exports.assembleValidators = assembleValidators;
exports.updateValidators = updateValidators;
exports.isValid = isValid;
exports.getValuesOf = getValuesOf;
exports.buildStateForField = buildStateForField;
exports.addFieldsToState = addFieldsToState;
exports.getValuesOf = getValuesOf;
exports.makeFieldProps = makeFieldProps;
exports.makePropsForStatus = makePropsForStatus;
exports.mapPropsToChild = mapPropsToChild;

var _react = require('react');
Expand Down Expand Up @@ -77,16 +81,24 @@ function isValid(value, validators) {
}, true);
}

function getValuesOf() {
var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

return Object.keys(obj).map(function (key) {
return obj[key];
});
}

function buildStateForField(fieldProps) {
var value = fieldProps.value,
valid = fieldProps.valid,
pristine = fieldProps.pristine;
var value = fieldProps.value;

var newState = { value: '', valid: false, pristine: true };
var newState = {
value: '',
valid: isValid(value, getValuesOf(assembleValidators(fieldProps))),
pristine: true
};

if (value !== undefined) Object.assign(newState, { value: value });
if (valid !== undefined) Object.assign(newState, { valid: valid });
if (pristine !== undefined) Object.assign(newState, { pristine: pristine });
return newState;
}

Expand All @@ -108,14 +120,6 @@ function addFieldsToState(component, child) {
}
}

function getValuesOf() {
var obj = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

return Object.keys(obj).map(function (key) {
return obj[key];
});
}

function makeFieldProps(child, onChange, state) {
if (typeof child.type === 'function' && child.type.name === 'Field') {
var name = child.props.name;
Expand All @@ -128,15 +132,40 @@ function makeFieldProps(child, onChange, state) {
return null;
}

function mapPropsToChild(child, type, propFunction) {
if (child.type === type || typeof child.type === 'function' && child.type.name === type) {
return _react2.default.cloneElement(child, propFunction(child));
function makePropsForStatus(status, state) {
return Object.keys(state).reduce(function (props, field) {
if (Object.prototype.hasOwnProperty.call(state[field], status)) {
return _extends({}, props, _defineProperty({}, field + '_' + status, state[field][status]));
}
return props;
}, {});
}

function mapPropsToChild(child, childPropsMap) {
var type = typeof child.type === 'function' ? child.type.name : child.type;
var childProps = {};
var newChildren = void 0;

if (child.props) {
if (childPropsMap.valid && child.props.valid) {
Object.assign(childProps, childPropsMap.valid());
}
if (childPropsMap.pristine && child.props.pristine) {
Object.assign(childProps, childPropsMap.pristine());
}
if (child.props.children) {
newChildren = _react2.default.Children.map(child.props.children, function (nestedChild) {
return mapPropsToChild(nestedChild, childPropsMap);
});
}
}
if (child.props && child.props.children) {
var newChildren = _react2.default.Children.map(child.props.children, function (nestedChild) {
return mapPropsToChild(nestedChild, type, propFunction);
});
return _react2.default.cloneElement(child, null, newChildren);

if (childPropsMap.Field && type === 'Field') {
return _react2.default.cloneElement(child, _extends({}, childPropsMap.Field(child), childProps), newChildren);
}
return child;
if (childPropsMap.input && type === 'input') {
return _react2.default.cloneElement(child, _extends({}, childPropsMap.input(child), childProps), newChildren);
}

return Object.keys(childProps).length || newChildren ? _react2.default.cloneElement(child, childProps, newChildren) : child;
}
11 changes: 8 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ React-formulize can be used to both quickly compose forms or add validation to e
2. A `Field` component can wrap (nested JSX) an `input` element (or a fragment containing an `input`) and control its underlying state automatically.
3. Pass validator props to the `Field` components. A `Field` component will keep track of its own validity.
4. Pass an `onSubmit` handler to `Form` in order to interact with the submission event. The callback will be passed a clone of the `Form`'s state.
5. Pass `valid` and `pristine` props to any nested child components in either a `Form` or `Field` component. These components will receive information about the `Form`'s status in the format of `${fieldName}_${statusType}` (e.g. name_valid & email_pristine).

#### Example: Composing A New Form With Custom Input Component(s)
```javascript
Expand Down Expand Up @@ -112,8 +113,10 @@ The `Form` component is a stateful higher-order-component which wraps presentati
The `Form` component will behave as follows with respect to its children:

1. Any `Field` tag will be passed the state associated with the `Field`'s name (`Form.state[child.props.name]`).
2. Any other component or element will be rendered with the props it would otherwise be passed.
3. Upon submission, `Form` will pass its `onSubmit` callback a clone of its current state.
2. Any component with a `valid` prop will be passed props stating the validity for all `Field`s in the `Form` (e.g. name_valid).
3. Any component with a `pristine` prop will be passed a props stating the pristine state for all `Field`s in the `Form` (e.g. email_pristine).
4. Any other component or element will be rendered with the props it would otherwise be passed.
5. Upon submission, `Form` will pass its `onSubmit` callback a clone of its current state.

*Note:* The `Form` component should be passed an `onSubmit` handler if you want to interact with the submission event!

Expand Down Expand Up @@ -184,7 +187,9 @@ The `Field` component will behave as follows with respect to its children:
2. Any `input` tag will be passed `name`, `type`, `value`, and `onChange` props.
3. If only a single direct child is passed to `Field`, it will be passed all of the relevant input props.
4. If multiple `input` tags are nested in a single `Field`, they would all share a single state (not recommended).

5. Any component with a `valid` prop will be passed a prop stating the `Field`'s validity (e.g. name_valid).
6. Any component with a `pristine` prop will be passed a prop stating the `Field`'s pristine state (e.g. email_pristine).

*Note:* Only one input element should be nested inside of a `Field` tag (see #4 above).

### Props
Expand Down
9 changes: 8 additions & 1 deletion src/components/Field.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
updateValidators,
getValuesOf,
mapPropsToChild,
makePropsForStatus,
} from '../helpers/utilities';

const Field = class extends React.Component {
Expand Down Expand Up @@ -112,7 +113,13 @@ const Field = class extends React.Component {
return (
<div>
{React.Children
.map(this.props.children, child => mapPropsToChild(child, 'input', () => inputProps))}
.map(this.props.children, child => mapPropsToChild(child, {
input: () => inputProps,
valid: () => makePropsForStatus('valid', { [this.props.name]: { valid: this.state.valid } }),
pristine: () => makePropsForStatus('pristine', {
[this.props.name]: { pristine: this.state.pristine },
}),
}))}
</div>
);
}
Expand Down
16 changes: 12 additions & 4 deletions src/components/Form.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import React from 'react';
import { addFieldsToState, mapPropsToChild, makeFieldProps } from '../helpers/utilities';
import {
addFieldsToState,
mapPropsToChild,
makeFieldProps,
makePropsForStatus,
} from '../helpers/utilities';

const Form = class extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -37,9 +42,12 @@ const Form = class extends React.Component {
.map(this.props.children, child =>
mapPropsToChild(
child,
'Field',
grandChild => makeFieldProps(grandChild, this.onFieldChange, this.state),
))}
{ Field: grandChild => makeFieldProps(grandChild, this.onFieldChange, this.state),
pristine: () => makePropsForStatus('pristine', this.state),
valid: () => makePropsForStatus('valid', this.state),
},
),
)}
</form>
);
}
Expand Down
60 changes: 44 additions & 16 deletions src/helpers/utilities.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@ export function isValid(value, validators) {
}, true);
}

export function getValuesOf(obj = {}) {
return Object.keys(obj).map(key => obj[key]);
}

export function buildStateForField(fieldProps) {
const { value, valid, pristine } = fieldProps;
const newState = { value: '', valid: false, pristine: true };
const { value } = fieldProps;
const newState = {
value: '',
valid: isValid(value, getValuesOf(assembleValidators(fieldProps))),
pristine: true,
};

if (value !== undefined) Object.assign(newState, { value });
if (valid !== undefined) Object.assign(newState, { valid });
if (pristine !== undefined) Object.assign(newState, { pristine });
return newState;
}

Expand All @@ -63,10 +69,6 @@ export function addFieldsToState(component, child, mounted = false) {
}
}

export function getValuesOf(obj = {}) {
return Object.keys(obj).map(key => obj[key]);
}

export function makeFieldProps(child, onChange, state) {
if (typeof child.type === 'function' && child.type.name === 'Field') {
const name = child.props.name;
Expand All @@ -79,14 +81,40 @@ export function makeFieldProps(child, onChange, state) {
return null;
}

export function mapPropsToChild(child, type, propFunction) {
if (child.type === type || (typeof child.type === 'function' && child.type.name === type)) {
return React.cloneElement(child, propFunction(child));
export function makePropsForStatus(status, state) {
return Object.keys(state).reduce((props, field) => {
if (Object.prototype.hasOwnProperty.call(state[field], status)) {
return { ...props, [`${field}_${status}`]: state[field][status] };
}
return props;
}, {});
}

export function mapPropsToChild(child, childPropsMap) {
const type = (typeof child.type === 'function') ? child.type.name : child.type;
const childProps = {};
let newChildren;

if (child.props) {
if (childPropsMap.valid && child.props.valid) {
Object.assign(childProps, childPropsMap.valid());
}
if (childPropsMap.pristine && child.props.pristine) {
Object.assign(childProps, childPropsMap.pristine());
}
if (child.props.children) {
newChildren = React.Children
.map(child.props.children, nestedChild => mapPropsToChild(nestedChild, childPropsMap));
}
}

if (childPropsMap.Field && type === 'Field') {
return React.cloneElement(child, { ...childPropsMap.Field(child), ...childProps }, newChildren);
}
if (child.props && child.props.children) {
const newChildren = React.Children.map(child.props.children, nestedChild => (
mapPropsToChild(nestedChild, type, propFunction)));
return React.cloneElement(child, null, newChildren);
if (childPropsMap.input && type === 'input') {
return React.cloneElement(child, { ...childPropsMap.input(child), ...childProps }, newChildren);
}
return child;

return (Object.keys(childProps).length || newChildren) ?
React.cloneElement(child, childProps, newChildren) : child;
}
51 changes: 51 additions & 0 deletions tests/components/Field.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,57 @@ describe('<Field /> Higher-Order-Component', () => {
});
});

describe('Passing down status', () => {
const TestComponent = () => <span>Test!</span>;
let wrapper;
let first;
let second;
let third;

beforeEach('Set up a basic form with testComponents in different configurations', () => {
wrapper = mount(
<Field name="name" value="" required>
<input />
<span>Hi There!</span>
<div>
<TestComponent valid />
</div>
<TestComponent pristine />
<TestComponent valid pristine>
<span>Hi There!</span>
</TestComponent>
</Field>,
);
first = wrapper.find(TestComponent).first();
second = wrapper.find(TestComponent).at(1);
third = wrapper.find(TestComponent).last();
});

it('passes validity information down to components with a `valid` prop', () => {
expect(first.props()).to.have.property('name_valid', false);

expect(second.props()).to.not.have.property('name_valid');
});

it('passes pristine information down to components with a `pristine` prop', () => {
expect(second.props()).to.have.property('name_pristine', true);

updateInput(wrapper, 'secondValue');
expect(second.props()).to.have.property('name_pristine', false);

expect(first.props()).to.not.have.property('name_pristine');
});

it('passes valid and pristine info down to components with flags', () => {
expect(third.props()).to.have.property('name_valid', false);
expect(third.props()).to.have.property('name_pristine', true);

updateInput(wrapper, 'Test Name');
expect(third.props()).to.have.property('name_pristine', false);
expect(third.props()).to.have.property('name_valid', true);
});
});

describe('`Field` lifecycle method tests', () => {
let wrapper;
let shouldUpdateSpy;
Expand Down
Loading