diff --git a/README.md b/README.md index c11f4e4..e1e7360 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ Base Form component for building convenient forms for [React](https://facebook.g ## Features Overview -- Controlled Form, i.e. it works with input values passed in props as JS object. +- Controlled Form, i.e. it accepts input values as a JSON object. - Simple API that handles deeply nested values and collections. -- Flexible and conventient validation that allows to validate inputs as user types. +- Flexible and convenient validation that allows to validate inputs as user types. - Allows to easily turn any existing component into a Form Input component. ## Installation @@ -26,177 +26,69 @@ npm install --save react-form-base `react-form-base` provides a `Form` base class which expects to work together with **Input** components. An **Input** is any component that consumes three properties: `value`, `error` and `onChange`. It also has to provide it's -`value` as first argument to `onChange` function supplied in props. For -existing ready-for-use input bindings take a look on [react-form-js](https://github.com/akuzko/react-form-js) -and [react-form-material-ui](https://github.com/akuzko/react-form-material-ui). +`value` as first argument to `onChange` function supplied in props. +*For existing ready-for-use input bindings take a look on:* +- [react-form-js](https://github.com/akuzko/react-form-js) +- [react-form-material-ui](https://github.com/akuzko/react-form-material-ui) ### Form Usage Most of form use-cases with examples are revealed in [**Demo Application**](https://akuzko.github.io/react-form-base/). Details on how to run it locally are at the end of README. -Bellow you can take a glance on main aspects of form usage: general API, -custom on-change handlers, validation and `$render` helper function. +#### Dedicated Forms -#### Basic example +Most of forms developers deal with are quite complicated and encapsulate +vast amount of validation and rendering logic. After some basic setup described +in the [Wiki](https://github.com/akuzko/react-form-base/wiki) your form may +look like following: ```js -import Form from 'react-form-base'; -import { TextField } from 'your-inputs'; // read on inputs in the beginning of README - -class MyForm extends Form { - render() { - return ( -
- - - - -
- ); - } -} -``` - -#### Nested fields example - -```js -import Form from 'react-form-base'; -import { TextField, Select } from 'your-inputs'; // read on inputs in the beginning of README -import countries from 'utils/countries'; // it's just a stub - -class MyForm extends Form { - render() { - return ( -
- - - - + + -If you don't have extra logic based on render method (such as implementing -rendering in base form and calling `super.render(someContent)` from child -forms), and you want to make things a little bit more DRY, you may declare -your form's rendering using `$render` method that accepts input-generation -function as argument. Thus, removing the `this.` prefix in inputs: - -```js -class MyForm extends Form { - $render($) { - return ( -
- - - +
); } } ``` -This form of rendering declaration is also very useful when working with -nested forms, since it has a special `nested` method that will generate -onChange handler for nested form for you: +#### Inline Forms + +If your form is small enough, you might want to render it inline instead of +defining separate form component. In this case you may pass renderer function +as only form's child. This function takes form's `$` function as argument for +convenience. Note that you still need to define static `validation` rules +for the Form to be able to use validations. ```js -{this.map('items', (_item, i) => - -)} +
+ {$ => ( +
+ + + +
+ )} +
``` -Of course, since `$` is argument in this method, you may use any name for -this variable that you find suitable. - #### API and helper methods - `$(name)`, `input(name)` - returns a set of properties for input with a given diff --git a/demo/src/App.jsx b/demo/src/App.jsx index b671633..ff4ac73 100644 --- a/demo/src/App.jsx +++ b/demo/src/App.jsx @@ -1,3 +1,4 @@ +/* global Promise */ import React, { PureComponent } from 'react'; import { Intro, InputPrerequisites } from './components'; import * as Forms from './forms'; diff --git a/demo/src/components/Source.jsx b/demo/src/components/Source.jsx index c4babcd..769197a 100644 --- a/demo/src/components/Source.jsx +++ b/demo/src/components/Source.jsx @@ -1,3 +1,4 @@ +/* global Prism */ import React, { PropTypes, PureComponent } from 'react'; export default class Source extends PureComponent { diff --git a/src/Form.jsx b/src/Form.jsx index 7cc0dab..914cae6 100644 --- a/src/Form.jsx +++ b/src/Form.jsx @@ -14,7 +14,9 @@ export default class Form extends PureComponent { clearErrorsOnChange: PropTypes.bool, validateOnChange: PropTypes.bool, validateOnSave: PropTypes.bool, - onRequestSave: PropTypes.func + onRequestSave: PropTypes.func, + validations: PropTypes.object, + children: PropTypes.func }; static defaultProps = { @@ -128,6 +130,10 @@ export default class Form extends PureComponent { return this.props.validateOnChange && this.state.hadErrors; } + get _validations() { + return this.props.validations || this.validations; + } + ifValid(callback) { const errors = this.getValidationErrors(); @@ -150,7 +156,7 @@ export default class Form extends PureComponent { } validate(validate) { - for (const name in this.validations) { + for (const name in this._validations) { validate(name); } @@ -213,6 +219,17 @@ export default class Form extends PureComponent { } render() { + const $bound = this._bind$(); + const { children: renderer } = this.props; + + if (typeof renderer === 'function') { + return renderer($bound); + } + + return this.$render($bound); + } + + _bind$() { const $bound = this.$.bind(this); Object.defineProperty($bound, 'nested', { @@ -226,7 +243,7 @@ export default class Form extends PureComponent { enumerable: false }); - return this.$render($bound); + return $bound; } $render() { diff --git a/src/utils.js b/src/utils.js index 570d806..926d53e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -19,7 +19,7 @@ function wildcard(name) { export function buildFormValidator(form) { function validate(name, options = {}) { const value = options.hasOwnProperty('value') ? options.value : form.get(name); - const validator = options['with'] || form.validations[name] || form.validations[wildcard(name)]; + const validator = options['with'] || form._validations[name] || form._validations[wildcard(name)]; if (!validator) return null; diff --git a/test/Form.test.js b/test/Form.test.js index b906686..f727586 100644 --- a/test/Form.test.js +++ b/test/Form.test.js @@ -559,4 +559,17 @@ describe('
', function() { expect(this.test.wrapper.state('form').foo).toEqual('new value'); }); }); + + describe('render', function() { + context('when function is passed', function() { + it('uses it as renderer function', function() { + const wrapper = shallow( + + {$ => } +
+ ); + expect(wrapper.containsMatchingElement()).toEqual(true); + }); + }); + }); });