redux-form

redux-form – библиотека для более быстрого и просто создания различных форм и управления ими.

Установка:

npm install --save redux-form

либо

yarn add redux-form

Как подключить?

  1. В файле, где мы создавали точку входа редьюсеров (т.е. объединяли их при помощи combineReducers) делаем импорт
    import { reducer as formReducer } from 'redux-form'
  2. Внутри этого combineReducers, где названия редьюсеров присваивались каким-то ключам, добавляем строчку
    form: formReducerВыгдеть это будет примерно так:

    let reducers = combineReducers({
        profilePage: profileReducer,
        dialogsPage: dialogsReducer,
        sidebar: sidebarReducer,
        usersPage: usersReducer,
        auth: authReducer,
        form: formReducer
    });

    Так у нас появилась еще одна веточка в стейте – form. Ключом обязательно дожен быть form, чтобы библиотека нас поняла. А если form по каким-то причинам занят, то можно заморочиться и найти инструкцию по замене на другое слово.

  3. Теперь в презентационной компоненте, где мы выводим нашу форму, нужно импортировать
    import { reduxForm, Field, reset } from 'redux-form';
  4. Обернуть нашу форму в “редаксовскую форму” – т.е. ХОКом reduxForm:
    import React from 'react';
    import { reduxForm, Field, reset } from 'redux-form';
    
    const LoginForm = (props) => {
        return (
          <form>
           //здесь идет чистый код нашей формы
          </form>
        )
    }
    
    const LoginReduxForm = reduxForm({form: 'login'})(LoginForm)
    
    const Login = (props) => {
        return <div>
            <h1>LOGIN</h1>
            <LoginReduxForm />
        </div>
    
    }
    
    export default Login;

    Как видим, здесь мы 1) вынесли в отдельную компоненту LoginForm без лишнего jsx кода наш код формы; 2) создали компоненту LoginReduxForm, в которой будет храниться наша “редаксовская форма” (обычная форма, обернутая ХОКом reduxForm); 3) присвоили уникальное имя нашей новой форме в параметре – {form: 'login'}   4) вывели эту компоненту в нужном месте.

На самом деле reduxForm – это не HOC, а функция, которая запускает его создание.

Как пользоваться?

  1. Все поля в форме меняем на компоненты Field, которые пришли к нам из библиотеки, с указанием component аналогично пропсам. Button не трогаем.
    Было до:
const LoginForm = (props) => {
    return (
        <form>
            <div>
                <input placeholder={"login"} name={"login"} />
            </div>
            <div>
                <input placeholder={"password"} name={"password"} />
            </div>
            <div>
            <input type={"checkbox"} name={"rememberMe"} />remember me
            </div>
            <div>
                <button>Login</button>
            </div>
        </form>
    )
}

Стало после:

const LoginForm = (props) => {
    return (
        <form>
            <div>
                <Field placeholder={"login"} name={"login"} component={"input"} />
            </div>
            <div>
                <Field placeholder={"password"} name={"password"} component={"input"} />
            </div>
            <div>
            <Field component={"input"} type={"checkbox"} name={"rememberMe"} />remember me
            </div>
            <div>
                <button>Login</button>
            </div>
        </form>
    )
}

Все. Теперь при вводе текста в форме в нашем стейте появляется временный объект login (имя нашей формы), который содержит много объектов, свойствами которых являются наши поля (берутся по name). Среди них объекты “было ли посещено поле”, “было ли тронуто” и т.д. А также объект values, где в свойства (берутся по name) попадает вводимый текст.

Объект reduxForm

Но в принципе нам изучать содержимое этого объекта не нужно. Он для библиотеки. Нужно просто знать, как им пользоваться.

  1. В пропсах теперь нам очень много всего приходит после оборачивания компоненты с формой в HOC. В т.ч. метод handleSubmit, который позволяет не перезагружать страницу при отправке.
    const LoginForm = (props) => {
        return (
            <form onSubmit={props.handleSubmit}>
    ....

    Это мы видим выше код вызова самой компоненты с формой еще без оборачивания. В нее в пропсах и пришел handleSubmit.

  2. А в месте вызова нашей компоненты с окончательной “редаксовской формой” теперь нужно повесить произвольную функцию черех onSubmit, которая соберет данные из полей из полей и обработает их при нажатии на кнопку:
    const Login = (props) => {
            const onSubmit = (formData) => {
              console.log(formData);  //здесь мы получаем данные с формы и будем из диспатчить потом
              dispatch(reset("login")); // очищаем форму   
            }
        return <div>
            <h1>LOGIN</h1>
            <LoginReduxForm onSubmit={nameFunction} /> 
        </div>
    
    }

     

Пример передачи даных с формы в ее временный стейт при помощи redux-form

В примере мы двигаемся “снизу в верх” – с вывода формы до передачи данных в стейт.

  1. Вывод формы и подключение reduxForm к ней:
    import React from 'react';
    import s from './MyPosts.module.css';
    import Post from './Post/Post';
    import { reduxForm, Field, reset } from 'redux-form';
    
    //презентационная компонента, в которую встраивается форма
    const MyPosts = (props) => {    
        let onAddPost = (e, dispatch) => {
          props.addPost(e.newPostBody);
          dispatch(reset("profileAddPostForm"));
        }
      return  (
                <div classmessage={s.postsBlock}>
              <h3>My posts</h3>
                <div>
                  <AddPostFormRedux onSubmit={onAddPost} />
                </div>
            </div>
        )
    }
    
    //компонента с формой
    const AddPostForm = (props) => {
      return (
        <form onSubmit={props.handleSubmit}>
          <div>
              <Field component="textarea" name="newPostBody" />
          </div>
            <div>
              <button>Add post</button>
            </div>
        </form>
      )
    }
    //применяем reduxForm к компоненте с формой и получаем новую особую компоненту
    const AddPostFormRedux = reduxForm({form: "profileAddPostForm"}) (AddPostForm);
    
    export default MyPosts;

    Как видно в коде, мы вешаем на форму в месте вызова экшн  addPost, внутри которого стрелочная функция первым параметром принимает объект с содержимым наших полей, а вторым – диспатч для ресета (очистки) после отправки, что не обязательно.

  1. Внутри контейнерной компоненты диспатчим экшн addPost, который ранее повесили на форму в месте ее вызова через onSubmit. В него при нажатии кнопки попадает наш вводимый текст, который потом в качестве параметра newPostText задиспатчивается через экшнкриэйтер addPostActionCreator в стейт:
    import React from 'react';
    import {addPostActionCreator} from '../../../redux/profile-reducer' ;
    import MyPosts from './MyPosts';
    import { connect } from 'react-redux';
    
    const mapStateToProps = (state) => {
      return {
        posts: state.profilePage.posts,
        newPostText: state.profilePage.newPostText
      }
    }
    
    //вот здесь происходит диспатч
    const mapDispatchToProps = (dispatch) => {
      return {
          addPost: (newPostText) => dispatch(addPostActionCreator(newPostText))
      }
    }
    
    const MyPostsContainer = connect(mapStateToProps, mapDispatchToProps) (MyPosts);
    
    export default MyPostsContainer;
  2. В редьюсере теперь мы получаем этот экшн и применяем
    import { usersAPI,  profileAPI } from "../api/api";
    
    const ADD_POST = 'ADD-POST';
    
    //в массиве posts примеры постов, которые мы добавляем еще туда
    let initialeState = {
        posts: [
            {id: 1, message: 'Hi, how are you'},
            {id: 2, message: 'Its is my first post'},
            {id: 3, message: 'Good'},
            {id: 4, message: 'How are you'},
            {id: 5, message: 'Im Fine'},
            {id: 6, message: 'And, you'},
        ],
    };
    
    //создаем новый пост в переменной newPost и добавляем
    const profileReducer = (state = initialeState, action) => {
        switch(action.type) {
            case ADD_POST: {
                let newPost =  {
                    id: 5,
                    message: action.newPostText,
                    likesCount: 0
                };
                return {
                    ...state,
                    posts: [...state.posts, newPost]     
                };
            }      
            default: 
                return state;  
        }
    }
    
    export const addPostActionCreator  = (newPostText) => ({type: ADD_POST, newPostText});
    
    export default profileReducer;

Как сделать валидацию?

Валидация может вешаться на поля  (Field-Level Validation), чтобы сразу после потери фокуса показывать предупреждение, а может на отправку формы (Submit Validation) и показвать предупреждения после нажатия на кнопку. Также она может быть ассинхронной, т.е. во время ввода идет проверка на сервере (Async Validation).

Мы рассмотрим, наверное, самую удобную для пользователя разновидность – Field-Level Validation, т. е. на поля.

  1. Создаем файл src/utils/validators/validators.js  Валидатор – это функция, которая получит value и ее можно прицепить к форме. Вот такие валидаторы мы и создадим в этом файле:
    export const required = value => {
        if (value) return undefined;
        return 'Field is required';
    }
    
    export const maxLengthCreator = (maxLength) => (value) => {
        if (value.length > maxLength) return `Max length is ${maxLength} sybols`;
        return undefined;
    }
  2. Теперь вешаем эти функции-валидаторы на поля (Field) в нужной нам форме, не забывая экспортировать. Валидатор required у нас заработает и так, а вот maxLengthCreator – это у нас не просто валидатор, а HOC для его создания. Поэтому на его основе нужно создать сам валидатор на произвольное количество символов за границами кода объявления этой формы. Так в примере ниже мы создали функцию на основе ХОКа для проверки макс. длины на 10 символов и уже ее добавили в качестве валидатора.
    const maxLength10 = maxLengthCreator(10);
    const AddPostForm = (props) => {
      return (
        <form onSubmit={props.handleSubmit}>
          <div>
              <Field component={Textarea} name="newPostBody" placeholder="Post message"
                     validate={[required, maxLength10]} />
          </div>
            <div>
              <button>Add post</button>
            </div>
        </form>
      )
    }

     

  3. Теперь валидация у нас работает – т.е. сообщение не отправится, если поле пустое либо количество символов больше максимального. Но, чтобы сделать пользователю сообщение об этом, нам нужна не просто textarea или input обычные указывать в качестве component у наших Field в форме. Мы должны создать свои “навороченные” textarea или input.
    Для этого создаем файл src/components/FormsControls/FormsControls.js (там где в соседней папочке хранился ранее созданный Preloader.js). В нем нашу Textarea и Input:

    import React from 'react';
    
    export const Textarea = ({input, meta, ...props}) => { //деструктуризируем в скобках пропсы
        return (
            <div>
                <textarea {...input} {...props} />
            </div>
        )
    }
    
    export const Input = ({input, meta, ...props}) => { //деструктуризируем в скобках пропсы
        return (
            <div>
                <input {...input} {...props} />
            </div>
        )
    }

    В скобках стрелочной функции мы выполняем деструктуризацию propsov (расскладаем их на элементы). Это нужно для того, чтобы во время return передать данные, например, placeholder, которые пришли к нам из места вывода наших полей. А также различную дополнительную информацию, например, было ли поле тронуто, изменено и т.п.

  4. Теперь в наши Field в качестве component мы передаем уже не обычную textarea или input, а нашу (не забываем экспортировать):
    <Field component={Textarea} name="newPostBody" placeholder="Post message"
                    validate={[required, maxLength10]} />
    
  5. Благодаря переданным в пропсах доп. данным мы теперь можем в нужный момент выводить предупреждения внутри созданных наших “навороченных” Textarea или Input. Например, вот так:
    import React from 'react';
    import styles from './FormsControls.module.css';
    
    export const Textarea = ({input, meta, ...props}) => { 
    
        const hasError = meta.touched && meta.error; //условие ошибки кладем в переменную для удобства
        return (
            <div className={styles.formControl + " " + (hasError ? styles.error : "")}>
                <div>
                    <textarea {...input} {...props} />
                </div>
                {hasError && <span>{meta.error}</span>} // в meta.error приходит текст ошибки
            </div>
        )
    }

    В класс стилей StyleSheet.error можна добавить красную рамку, цвет поля или т.п.

  6. Это ниже уже идет пример попытки избежать дублирования кода для Input и Textarea в нашем файле FormsControls.js. Но решение не идеальное:/
    import React from 'react';
    import styles from './FormsControls.module.css';
    
    
    const FormControl = ({input, meta, child, ...props}) => {
        const hasError = meta.touched && meta.error;
        return (
            <div className={styles.formControl + " " + (hasError ? styles.error : "")}>
               <div>
                    {props.children}
                </div> 
               {hasError && <span>{meta.error}</span> } 
            </div>
        )
    }
    
    export const Textarea = (props) => {
        const {input, meta, child, ...restProps} = props;
        return <FormControl {...props}> <textarea {...input} {...restProps} /></FormControl>
    }
    
    export const Input = (props) => {
        const {input, meta, child, ...restProps} = props;
        return <FormControl {...props}> <input {...input} {...restProps} /></FormControl> 
    }

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

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