Skip to content
This repository has been archived by the owner on Jul 5, 2019. It is now read-only.

Commit

Permalink
📝 docs
Browse files Browse the repository at this point in the history
  • Loading branch information
deepsweet authored Jun 8, 2017
1 parent 7d58c35 commit b882bed
Showing 1 changed file with 344 additions and 9 deletions.
353 changes: 344 additions & 9 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,360 @@

Better form state management for React in which data state can be directly mapped to form fields, so form becomes just a representation and changing interface for that data state, as it should be.

## Status
TOC

This is a [monorepo](https://github.com/babel/babel/blob/master/doc/design/monorepo.md) composed of these packages:
* [Usage](#usage)
* [Intro](#intro)
* [`Field`](#field)
* [`Form`](#form)
* [App](#app)
* [Validation](#validation)
* [`FieldValidation`](#fieldvalidation)
* [`FormValidation`](#formvalidation)
* [Validators](#validators)
* [Demo](#demo)
* [Status](#status)
* [Development](#development)

| package | version | description |
| ------- | ------- | ----------- |
| [neoform](packages/neoform/) | [![npm](https://img.shields.io/npm/v/neoform.svg?style=flat-square)](https://www.npmjs.com/package/neoform) | Core toolkit with `Form` and `Field` HOCs |
| [neoform-validation](packages/neoform-validation/) | [![npm](https://img.shields.io/npm/v/neoform-validation.svg?style=flat-square)](https://www.npmjs.com/package/neoform-validation) | `FormValidation` and `FieldValidation` HOCs |
| [neoform-plain-object-helpers](packages/neoform-plain-object-helpers/) | [![npm](https://img.shields.io/npm/v/neoform-plain-object-helpers.svg?style=flat-square)](https://www.npmjs.com/package/neoform-plain-object-helpers) | `getByFieldName` and `setByFieldName` helpers for plain object state |
| [neoform-immutable-helpers](packages/neoform-immutable-helpers/) | [![npm](https://img.shields.io/npm/v/neoform-immutable-helpers.svg?style=flat-square)](https://www.npmjs.com/package/neoform-immutable-helpers) | `getByFieldName` and `setByFieldName` helpers for Immutable.js state |
## Usage

### Intro

Let's say you have some data structure:

```json
"user": {
"name": "Pepe",
"status": "sad",
"friends": [
"darkness"
]
}
```

And you want to represent this data as HTML form with an Input for each data field. First, let's try to represent this data ↔ form relations in kinda declarative way, with strings:

```js
"user": {
"name": "Pepe", // "user.name"
"status": "sad", // "user.status"
"friends": [
"darkness" // "user.friends[0]"
]
}
```

You might be familiar with this as "key path" on immutable data structures or helpers like `lodash.get()`.

And here is the main core idea of NeoForm: to directly "map" data fields to particular form fields. Let's start with creating a "field" and see how it works with step-by-step example:

### `Field`

```js
const MyInput = () => (
<input />
);

export default MyInput;
```

If you wrap it with a `Field` [HOC](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) provided by NeoForm it will provide a few props:

#### `value`

Actual value of field from data structure (map it to checkbox `checked` attribute if it's boolean and so on).

```js
import { Field } from 'neoform';

const MyInput = ({ value }) => (
<input value={value} />
);

export default Field()(MyInput);
```

#### `onChange`

Handler which should tell NeoForm about how to get that actual `value` (use `(e) => e.target.checked` if you have a checkbox or just `(value) => value` if you have some custom/3rd-party field implementation).

```js
import { Field } from 'neoform';

const MyInput = ({ value }) => (
<input value={value} />
);

export default Field(
(e) => e.target.value
)(MyInput);
```

### `Form`

Let's add our fields to a simple form:

```js
import MyInput from '../MyInput';

const MyForm = ({ data }) => (
<form>
<MyInput name="user.name" />
<MyInput name="user.status" />
<MyInput name="user.friends[0]" />
</form>
);

export default MyForm;
```

And wrap it with a `Form` HOC provided by NeoForm:

```js
import { Form } from 'neoform';

import MyInput from '../MyInput';

const MyForm = (/* { data } */) => (
<form>
<MyInput name="user.name" />
<MyInput name="user.status" />
<MyInput name="user.friends[0]" />
</form>
);

export default MyForm(
(data, name) => // return a value from data by field name
)(MyForm);
```

Where `data` is actual data structure which you should provide to the MyForm from outside and `name` is a particular MyInput name. NeoForm should be taught on how to get a value by name because you might have a plain object data, Immutable or something else with a different "interface":

```js
import { Form } from 'neoform';
import { getByFieldName } from 'neoform-plain-object-helpers';

import MyInput from '../MyInput';

const MyForm = (/* { data } */) => (
<form>
<MyInput name="user.name" />
<MyInput name="user.status" />
<MyInput name="user.friends[0]" />
</form>
);

export default MyForm(getByFieldName)(MyForm);
```

In this case it's just a `lodash.get(data, name)` under the hood.

### App

```js
import { setByFieldName } from 'neoform-plain-object-helpers';

import MyForm from '../MyForm';

class App extends Component {
constructor(props) {
super(props);

this.state = {
data: props.data
};

this.onChangeHandler = this.onChangeHandler.bind(this);
}

onChangeHandler(name, value) {
// update data field in state with new value by field name
this.setState((prevState) => setFieldByName(state, name, value))
}

onSubmit() {
console.log('Submit!')
}

render() {
<MyForm
data={this.state.data}
onChange={this.onChangeHandler}
onSubmit={this.onSubmit}
/>
}
}
```

What's going on here? The second idea of NeoForm: to have only one `onChange` handler for the entire form instead of having multiple hanlders for each field, so you can update state when some field was updated. And then after a new render fields will receive their new values by `name`.

```
+--------------+
| |
| |
| +---------v---------+
| | |
| | MyForm.data |
| | |
| +---------+---------+
| |
| name |
| |
| +---------v---------+
| | |
| | MyInput.value |
| | |
| +---------+---------+
| |
| |
| +---------v---------+
| | |
| | MyInput.onChange |
| | |
| +---------+---------+
| |
| name | value
| |
| +---------v---------+
| | |
| | MyForm.onChange |
| | |
| +---------+---------+
| |
| name | value
| |
+--------------+
```

### Validation

Validation in NeoForm is always asynchronous.

#### `FieldValidation`

`FieldValidation` is another HOC:

```js
import { Field } from 'neoform';
import { FieldValidation } from 'neoform-validation';
import { compose } from 'recompose';

const MyInput = ({
validate,
validationStatus,
validationMessage,
...props
}) => (
<input {...props} onBlur={validate} />
{
validationStatus === false && (
<span>{validationMessage}</span>
)
}
)

export default compose(
Field((e) => e.target.value),
FieldValidation()
)(MyInput)
```

Where:

* `validate` – validation action, should be called whenever you want (`onChange`, `onBlur`, etc)
* `validationStatus``true` | `false` | `undefined` status of field validation
* `validationMessage` – valid/invalid message passed to validator 
* `compose(…)` – same as manually composed HOCs like `Field(…)(FieldValidation()(MyInput))` but using awesome [recompose](https://github.com/acdlite/recompose)

#### `FormValidation`

```js
import { Form } from 'neoform';
import { getByFieldName } from 'neoform-plain-object-helpers';
import { FormValidation } from 'neoform-validation';

import MyInput from '../MyInput';

const MyForm = ({
/* data, */
validate,
validationStatus,
onSubmit
}) => (
<form onSubmit={(e) => {
e.preventDefault();
validate(onSubmit)
}}>
<MyInput name="user.name" />
<MyInput name="user.status" />
<MyInput name="user.friends[0]" />
</form>
);

export default compose(
MyForm(getByFieldName),
FormValidation()
)(MyForm);
```

Where:

* `validate` – entire form validation action which will call all fields validation and then provided callback if they are valid (your `onSubmit` handler in most cases)
* `validationStatus``true` | `false` | `undefined` status of entire form validation

#### Validators

Validation process in NeoForm is always asynchronous and "validator" is just a Promise. Rejected one is for `validationStatus: false` prop and resolved is for `validationStatus: true`. `validationMessage` prop is a message passed on fulfilling a Promise.

```js
export const requiredValidator = (value) => {
if (value === '') {
return Promise.reject('💩');
}

return Promise.resolve('🎉');
};
```

It's up to you on how to manage multiple validators, with `Promise.all()` or some sequence.

This validator can be passed as `validator` prop to fields then:

```js
import { requiredValidator } from '../validators'

//

<form>
<MyInput name="user.name" validator={requiredValidator} />
<MyInput name="user.status" />
<MyInput name="user.friends[0]" />
</form>

//
```

## Demo

It's a good idea to play around with all this information with live examples:

```sh
yarn start demo neoform
yarn start demo neoform-validation
```

## Build
## Status

This is a [monorepo](https://github.com/babel/babel/blob/master/doc/design/monorepo.md) composed of these packages:

| package | version | description |
| ------- | ------- | ----------- |
| [neoform](packages/neoform/) | [![npm](https://img.shields.io/npm/v/neoform.svg?style=flat-square)](https://www.npmjs.com/package/neoform) | Core toolkit with `Form` and `Field` HOCs |
| [neoform-validation](packages/neoform-validation/) | [![npm](https://img.shields.io/npm/v/neoform-validation.svg?style=flat-square)](https://www.npmjs.com/package/neoform-validation) | `FormValidation` and `FieldValidation` HOCs |
| [neoform-plain-object-helpers](packages/neoform-plain-object-helpers/) | [![npm](https://img.shields.io/npm/v/neoform-plain-object-helpers.svg?style=flat-square)](https://www.npmjs.com/package/neoform-plain-object-helpers) | `getByFieldName` and `setByFieldName` helpers for plain object state |
| [neoform-immutable-helpers](packages/neoform-immutable-helpers/) | [![npm](https://img.shields.io/npm/v/neoform-immutable-helpers.svg?style=flat-square)](https://www.npmjs.com/package/neoform-immutable-helpers) | `getByFieldName` and `setByFieldName` helpers for [Immutable](https://github.com/facebook/immutable-js) state |

## Development

```sh
yarn start build neoform
Expand Down

0 comments on commit b882bed

Please sign in to comment.