Думаю, большинство знает схему работы библиотеки redux:
view -> action -> middlewares -> reducers -> state -> view
Подробности здесь
Хочу представить вашему вниманию библиотеку, которая работает по тому же принципу для форм.
Документация на английском.
Устанавливаем:
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/
Добавить комментарий