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-run script-with-build;

І пробуємо CRUD додаток з використанням redux-form за посиланням localhost(127.0.0.1):4000/app/.

При асинхронній валідації можливий конфуз: при натисканні сабміта до відповіді з сервера сабміт спрацює.

документації є ще багато цікавого і корисного. Рекомендую до перегляду.

P. S.: Як завжди чекаю конструктиву.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.