UNPKG

yarfl

Version:

Yet Another Redux Forms Library

222 lines (181 loc) 8.61 kB
# <img src="./header.svg"> ##### Yet Another Redux Forms Library ##### Manage form state in React/Redux applications with reactive updates and field level validation. Heavily inspired by [MobX React Form](https://github.com/foxhound87/mobx-react-form) [![npm](https://img.shields.io/npm/v/yarfl.svg)]() [![node](https://img.shields.io/node/v/yarfl.svg)]() [![GitHub license](https://img.shields.io/github/license/axelyung/yarfl.svg)]() [![Downloads](https://img.shields.io/npm/dt/yarfl.svg)]() [![Downloads](https://img.shields.io/npm/dm/yarfl.svg)]() [![NPM](https://nodei.co/npm/yarfl.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/yarfl/) ## Warning: experimental, not ready for production ## Features - Easy and JSON-serializable configuration. - Reactive updates with input binding. - Reactive validation with error messages. - Supports both sync and async validation. - Array fields. - Nested fields. - Easily integrated with component libraries (Material Ui, React Widgets, React Select, etc). - Adheres to functional and immutable design principles. - Written in Typescript. ## Documentation See the complete documentation [here](https://axelyung.github.io/yarfl) ## Demo There is a working live demo of this library's implementation [here](https://axelyung.github.io/yarfl-examples) with accompanying [source code](https://github.com/axelyung/yarfl-examples). ## Why? Neither [Redux Form](https://github.com/erikras/redux-form) nor [React Redux Form](https://github.com/davidkpiano/react-redux-form) (the two most popular React/Redux form libraries) provide an easy way of creating serializable form configurations. Furthermore, both libraries require the use of their own form and/or input components leading to unnecessary complexity. This library attempts to solve the first issue by dynamically creating a reducer function from serializable configurations and the second by exposing an API that does not *require* the need for special React components. The benefit is that more of the forms' functionality can be abstracted to the configuration rather than having to create individual form/field components. ## Getting started From the [getting started](https://axelyung.github.io/yarfl/getting-started.html) section of the documentation. 1. Install dependencies (`redux`, `react`, `react-redux`, `redux-thunk` are peer dependencies): ```bash yarn add redux react react-redux redux-thunk yarfl ``` 2. Define a config object with a `fields` property describing what fields should be part of the Redux state and how they should be validated. The `rules` string describes which rules from [validatorjs](https://github.com/skaterdav85/validatorjs#available-rules) the field should be validated against (optional). ```javascript //config.js export const myFormConfig = { name: 'myForm', fields: { name: { rules: 'required' }, email: { rules: 'required|email' }, age: { rules: 'required|min:18' } } } ``` 3. Create a reducer, initial state and a connector by passing the `config` object to the `init` function and then simply create the Redux store as you normally would: ```javascript // store.js import { init } from 'yarfl'; import { createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import { myFormConfig } from './config' // create a reducer, initial state and a connector const { reducer, initialState, connect } = init(myFormConfig); // redux-thunk is a peer dependency of yarfl const enhancers = applyMiddleware(thunk) const store = createStore(reducer, initialState, enhancers); export { store, connect } ``` 4. Set up the [`Provider`](https://github.com/reduxjs/react-redux/blob/master/docs/api.md#provider-store) as you normally would with React Redux. To connect a React component with the store from the previous step use the `connect` method that we created with `init` in step three. This `connect` method doesn't require `mapStateToProps` or `mapDispatchToProps` arguments, this is taken care of by Yarfl automatically. Just pass the React component as its first and only argument. ```jsx // App.js import React from 'react'; import { Provider } from 'react-redux'; import { store } from './store.js' import MyFormComponent from './MyFormComponent.js'; class App extends React.Component { render() { return ( <Provider store={store}> <MyFormComponent /> </Provider> ) } } ``` ```jsx // MyFormComponent.js import React from 'react'; import { connect } from './store.js'; class MyFormComponent extends React.Component { ... } export default connect(MyComponent) ``` 5. When connected, a form prop of the name specified in the config object (in our case `myForm`) is passed to the React component. The `myForm` prop is an object containing a handful of properties and methods to interact with the store. ```jsx class MyComponent extends React.Component { render () { const { select, valid, errors } = this.props.myForm; /** * The 'select' function accepts a key string and * returns an object containing the name field * object from the store with a 'bind' function. */ const nameField = select('name'); return ( <form> <div> {/* The 'bind' function returns an object of properties that can be spread on an input element effectively binding that component to update and track the state. */} <label>{nameField.label}</label><br/> <input {...nameField.bind()} /> </div> {/* The 'valid' property describes if all fields registered in the Redux store pass their validation rules. */} <button type="submit" disabled={!valid} /> <div> {/* The 'errors' array contains the first error message (if any) for all registered fields. */} <label>All errors:</label> <ul> {errors.map(err => <li>{err}</li>)} </ul> </div> </form> ) } } ``` The `bind` method returns an object containing input attributes (with `"value"`) from the store and `onChange`, `onBlur` and `onFocus` handlers to dispatch update actions. ```javascript const bindProps = select('name').bind(); /* bindProps = { value: '', default: '', id: 'name', name: 'name', type: 'text', label: 'Name', className: '', placeholder: 'Name', disabled: false, autoFocus: false, onChange: (e: InputEvent, value?: any) => void, onBlur: () => void, onFocus: () => void, } */ ``` Using the spread syntax will attach all the props to the input element, but you are free to choose which properties you want to use or create new ones from `bind`'s return values: ```jsx render() { const { select } = this.props; const { id, value, onChange } = select('email').bind(); return ( ... <input id={`${id}-is-the-id`} name="custom-email-name" value={value} onChange={(e, value) => { console.log('About to dispatch...') onChange(e); console.log('Dispatch complete!') }} /> ... ) } ``` 6. To initiate more than one form just add more config objects as input parameters from the `init` function from step three. ```javascript import { loginFormConfig, newUserConfig, ... } from './configs' // pass one or more config objects to init const { reducer, initialState, connect } = init(loginFormConfig, newUserConfig, ...); ```