Redux-form. Когда работать с формами просто

от автора

Думаю, большинство знает схему работы библиотеки redux:
view -> action -> middlewares -> reducers -> state -> view
Подробности здесь
Хочу представить вашему вниманию библиотеку, которая работает по тому же принципу для форм.
image

Документация на английском.
Устанавливаем:

 npm install redux-form 

Подключаем в наше приложение:

import { createStore, combineReducers } from 'redux' import { reducer as formReducer } from 'redux-form'  const reducers = {   // ваши редюсеры   form: formReducer     // В state все данные формы будут храниться в свойстве form } const reducer = combineReducers(reducers) const store = createStore(reducer) 

Создаем форму:

import React, { Component } from 'react'; // берем компонент поля (Field) и провайдер для формы (reduxForm) import { Field, reduxForm } from 'redux-form';  class Form extends Component {     render(){         // по умолчанию handleSubmit принимает функцию обработчик         // reset скидывает значения до значений, заданных во время инициализации         // в данном случае до undefined, так как значение не задано         const {handleSubmit, reset} = this.props;          const submit = (values) => console.log(values);          return (             <form onSubmit={handleSubmit(submit)}>                 {/* принимает имя поля, тип и остальные свойства, которые расмотрим позже*/}                 <Field name="title" component="input" type="text"/>                 <Field name="text" component="input" type="text"/>                 <div>                     <button type="button" onClick={reset}>Очистить форму</button>                     <button type="submit">Отправить форму</button>                 </div>             </form>         );     } } Form = reduxForm({     form: 'post', // имя формы в state (state.form.post) })(Form);  export default Form; 

Расмотрим ситуацию, когда нужно прокинуть обработчик с компонента уровнем выше:
Создадим компонент:

import React, { Component }  from 'react';  import Form from './Form'  class EditPost extends Component{     constructor(props) {         super(props);     }         handleSubmit = (values) => {         console.log(values);     };     render() {         let {post, dispatch} = this.props;         return (             <div>                 {/* передаем обработчик*/}                 <Form onSubmit={this.handleSubmit} />             </div>         );     } } 

И изменим нашу форму:

// меняем <form onSubmit={handleSubmit(submit)}> на  <form onSubmit={handleSubmit}> 

Если нам надо задать значение, при инициализации используем actionCreator initialize, который принимает первым параметром название формы, вторым объект с значениями. Например, для статьи по id:

import React, { Component }  from 'react'; // подключаем метод import {initialize} from 'redux-form'; import {connect} from 'react-redux';  import Form from './Form'  class EditPost extends Component{     constructor(props) {         super(props);         // post = {title: " Текст заголовка ", text: " Текст статьи "}         let {post, initializePost} = this.props;         // инициализация         initializePost(post);     }         handleSubmit = (values) => {         console.log(values);     };     render() {         return (             <div>                 <Form onSubmit={this.handleSubmit} />             </div>         );     } } // прокидываем в props функцию для инициализации формы function mapDispatchToProps(dispatch){     return {         initializePost: function (post){             dispatch(initialize('post', post));         }     } } // прокидываем в props объект для инициализаци формы function mapStateToProps(state, ownProps){     const id = ownProps.params.id;     return {        post: state.posts[id]     } } export default connect(mapStateToProps, mapDispatchToProps)(EditPost); 

Остальные action creators можно посмотреть здесь.

Если нас не устраивает стандартное поле, мы можем передавать свой вариант верстки и действий:

import React, { Component } from 'react'; import { Field, reduxForm } from 'redux-form';  class Form extends Component {    // функция, которая возвращает свою реализацию    renderField = ({ input, label, type}) => (         <div>             <label>{label}</label>             <div>                 <input                     {...input} placeholder={label} type={type}/>             </div>         </div>     );     render(){         const {handleSubmit, reset} = this.props;          return (             <form onSubmit={handleSubmit}>                 {/* принимает функцию с реализацией поля*/}                 <Field name="title" component={this.renderField}  label="Заголовок" type="text"/>                 <Field name="text" component={this.renderField}  label="Текст" type="text"/>                 <div>                     <button type="button" onClick={reset}>Очистить форму</button>                     <button type="submit">Отправить форму</button>                 </div>             </form>         );     } } Form = reduxForm({     form: 'post' })(Form);  export default Form; 

Подробнее про компонент Field.

Redux-form поддерживает три вида валидации:

  • Синхронная валидация
  • Асинхронная валидация
  • Валидация во время сабмита

Для синхронной и асинхронной валидации создадим файл formValidate.js:

// синхронная валидация export const validate = values => {     const errors = {};     if(!values.text){         errors.text = 'Поле обязательно для заполнения!';     } else if (values.text.length < 15) {         errors.text = 'Текст должен быть не менее 15 символов!'     }     // для синхронной валидации нужно вернуть объект с ошибками     return errors };  const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) //асинхронная валидация //принимает два параметра значения и redux dispatch export const asyncValidate = (values/*, dispatch */) => {     return sleep(1000) // имитация серверного ответа         .then(() => {             if (!values.title) {                 // для асинхронной валидации нужно бросить объект с ошибкой                 throw {title: 'Поле обязательно для заполнения!'}             } else if (values.title.length > 10) {                 throw {title: 'Заголовок должен быть не более 10 символов!'}             }         }) }; 

Для валидации во время сабмита нужно изменить обработчик сабмита так, чтобы он возвращал промис:

import React, { Component }  from 'react'; // подключаем класс ошибки для формы import {initialize, SubmissionError} from 'redux-form'; import {connect} from 'react-redux'; import Form from './Form';  const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); class EditPost extends Component{     constructor(props) {         super(props);     }         handleSubmit = (values) => {         /* возвращаем промис            erros в нашем случае это объект, в котором ключ - это название поля с ошибкой            Например, {title: "Неверно введен заголовок"}*/         return sleep(1000) {// симуляция ответа сервера}           .then(({errors, ...data}) => {              if (errors) {                 // бросаем экземпляр класса ошибки с текстами ошибок                 // _error общая ошибка для формы                 throw new SubmissionError({ ...errors, _error: 'Статья не добавлена!' })              } else {                 // ошибок нет, обрабатываем данные data             }        })     };     render() {         return (             <div>                 {/* передаем обработчик*/}                 <Form onSubmit={this.handleSubmit} />             </div>         );     } } function mapDispatchToProps(dispatch){     return {         initializePost: function (post){             dispatch(initialize('post', post));         }     } } function mapStateToProps(state, ownProps){     const id = ownProps.params.id;     return {        post: state.posts[id]     } } export default connect(mapStateToProps, mapDispatchToProps)(EditPost); 

А теперь подключим валидацию и организуем вывод ошибок:

import React, { Component } from 'react'; import { Field, reduxForm } from 'redux-form'; import {validate, asyncValidate} from '../formValidate';  class Form extends Component {    renderField = ({ input, label, type, meta: { touched, error, warning }}) => (         <div>             <label>{label}</label>             <div>                 <input {...input} placeholder={label} type={type}/>                 {/* ошибка для поля*/}                 {touched && ((error && <div>{error}</div>))}             </div>         </div>     );     render(){         const {handleSubmit, reset, error} = this.props;          return (             <form onSubmit={handleSubmit}>                 {/* принимает функцию с реализацией поля*/}                 <Field name="title" component={this.renderField}  label="Заголовок" type="text"/>                 <Field name="text" component={this.renderField}  label="Текст" type="text"/>                 <div>                     <button type="button" onClick={reset}>Очистить форму</button>                     <button type="submit">Отправить форму</button>                     {/*общая ошибка для формы*/}                     {error && <div>{error}</div>}                 </div>             </form>         );     } }  Form = reduxForm({     form: 'post',     // подключение валидации     validate,     asyncValidate })(Form);  export default Form; 

Для тех, кто хочет посмотреть пример работы, делаем так:

 git clone https://github.com/BoryaMogila/koa_react_redux.git;  git checkout redux-form;  npm install;  npm run-script run-with-build; 

И пробуем CRUD приложение с использованием redux-form по ссылке localhost(127.0.0.1):4000/app/.

При асинхронной валидации возможен конфуз: при нажатии сабмита до ответа с сервера сабмит сработает.
В документации есть еще много интересного и полезного. Рекомендую к просмотру.
P.S.: Как всегда жду конструктива.
ссылка на оригинал статьи https://habrahabr.ru/post/313966/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *