LoginSignup
6

More than 3 years have passed since last update.

React FormikでFormの各部品のcomponentを制作

Last updated at Posted at 2020-08-17

先日、「React Formikの入門編」の記事を投稿しました。
その記事をもとにformの各部品(input、textarea、select)などをcomponentとして分けたので共有します。
※実際にアプリケーションを開発する際はこのようにcomponent分けをするのではないでしょうか。

FormContainer

  • form画面の親コンポーネント
  • initialValuesvalidationSchemaonSubmitを定義
  • 呼び出し先に送るpropsを設定
FormContainer.js
import { Form, Formik } from 'formik';
import {
  checkboxOptions,
  dropdownOptions,
  radioOptions,
} from '../constants/formOptions';

import FormControl from './FormControl';
import React from 'react';
import SubmitButton from './SubmitButton';
import { initialValues } from '../validation/initialValues';
import { validationSchema } from '../validation/validationSchema';

function FormContainer() {
  const onSubmit = (values) => {
    console.log('form data', values);
    console.log('Saved data', JSON.parse(JSON.stringify(values)));
  };

  return (
    <Formik
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={onSubmit}
    >
      {(formik) => (
        <Form>
          <FormControl
            control="input"
            name="email"
            label="Email"
            type="email"
          />
          <FormControl
            control="textarea"
            name="description"
            label="Description"
          />
          <FormControl
            control="select"
            label="Select a topic"
            name="selectOption"
            options={dropdownOptions}
          />
          <FormControl
            control="radio"
            label="Radio topic"
            name="radioOption"
            options={radioOptions}
          />
          <FormControl
            control="checkbox"
            label="Checkbox topics"
            name="checkboxOption"
            options={checkboxOptions}
          />
          <FormControl
            control="fieldArrayInput"
            label="multi topic"
            name="fieldArrayInput"
          />
          <FormControl
            control="fileInput"
            label="file upload"
            name="fileInput"
          />
          <FormControl control="date" label="Pick a date" name="birthDate" />
          <SubmitButton formik={formik} />
        </Form>
      )}
    </Formik>
  );
}

export default FormContainer;

FormControl

  • formの部品によって各コンポーネントに振り分ける
FormControl.js
import CheckboxGroup from './CheckboxGroup';
import DatePicker from './DatePicker';
import FieldArrayInput from './FieldArrayInput';
import FileInput from './FileInput';
import Input from './Input';
import RadioButtons from './RadioButtons';
import React from 'react';
import Select from './Select';
import Textarea from './Textarea';

function FormikControl(props) {
  const { control, ...rest } = props;
  switch (control) {
    case 'input':
      return <Input {...rest} />;
    case 'textarea':
      return <Textarea {...rest} />;
    case 'select':
      return <Select {...rest} />;
    case 'radio':
      return <RadioButtons {...rest} />;
    case 'checkbox':
      return <CheckboxGroup {...rest} />;
    case 'fieldArrayInput':
      return <FieldArrayInput {...rest} />;
    case 'fileInput':
      return <FileInput {...rest} />;
    case 'date':
      return <DatePicker {...rest} />;
    default:
      return null;
  }
}

export default FormControl;

Input

Input.js
import { ErrorMessage, Field } from 'formik';

import React from 'react';

function Input(props) {
  const { label, name, ...rest } = props;
  return (
    <div className="form-control">
      <label htmlFor={name}>{label}</label>
      <Field id={name} name={name} {...rest} />
      <ErrorMessage name={name} />
    </div>
  );
}

export default Input;

Textarea

  • Filedコンポーネントのpropsasで「textarea」を指定
Textarea.js
import { ErrorMessage, Field } from 'formik';

import React from 'react';

function Textarea(props) {
  const { label, name, ...rest } = props;
  return (
    <div className="form-control">
      <label htmlFor={name}>{label}</label>
      <Field as="textarea" id={name} name={name} {...rest} />
      <ErrorMessage name={name} />
    </div>
  );
}

export default Textarea;

Select

  • Filedコンポーネントのpropsasで「select」を指定
Select.js
import { ErrorMessage, Field } from 'formik';

import React from 'react';

function Select(props) {
  const { label, name, options, ...rest } = props;
  return (
    <div className="form-control">
      <label htmlFor={name}>{label}</label>
      <Field as="select" id={name} name={name} {...rest}>
        {options.map((option) => (
          <option key={option.value} value={option.value}>
            {option.key}
          </option>
        ))}
      </Field>
      <ErrorMessage name={name} />
    </div>
  );
}

export default Select;

Radio Button

RadioButtons.js
import { ErrorMessage, Field } from 'formik';

import React from 'react';

function RadioButtons(props) {
  const { label, name, options, ...rest } = props;
  return (
    <div className="form-control">
      <label htmlFor={name}>{label}</label>
      <Field id={name} name={name} {...rest}>
        {({ field }) => {
          return options.map((option) => (
            <React.Fragment key={option.key}>
              <input
                id={option.value}
                type="radio"
                {...field}
                value={option.value}
                checked={field.value === option.value}
              />
              <label htmlFor={option.value}>{option.key}</label>
            </React.Fragment>
          ));
        }}
      </Field>
      <ErrorMessage name={name} />
    </div>
  );
}

export default RadioButtons;

Checkbox

CheckboxGroup.js
import { ErrorMessage, Field } from 'formik';

import React from 'react';

function CheckboxGroup(props) {
  const { label, name, options, ...rest } = props;
  return (
    <div className="form-control">
      <label htmlFor={name}>{label}</label>
      <Field id={name} name={name} {...rest}>
        {({ field }) => {
          return options.map((option) => (
            <React.Fragment key={option.key}>
              <input
                id={option.value}
                type="checkbox"
                {...field}
                value={option.value}
                checked={field.value.includes(option.value)}
              />
              <label htmlFor={option.value}>{option.key}</label>
            </React.Fragment>
          ));
        }}
      </Field>
      <ErrorMessage name={name} />
    </div>
  );
}

export default CheckboxGroup;

FieldArray

  • 「+」「-」ボタンを押下すると動的にinputが増減するコンポーネント
  • 1つ目のinputで「-」ボタンを押下できない
  • 1つ目のinputに値が入ってない場合はバリデーションエラーとなる
FieldArrayInput.js
import { ErrorMessage, Field, FieldArray } from 'formik';

import React from 'react';

function FieldArrayInput(props) {
  const { label, name } = props;

  const validateArrayInput = (value) => {
    let error;
    if (!value) {
      error = 'FieldArrayInput is Required';
    }
    return error;
  };

  return (
    <div className="form-control">
      <label htmlFor={name}>{label}</label>
      <FieldArray name={name}>
        {(fieldArrayProps) => {
          const { push, remove, form } = fieldArrayProps;
          const { values } = form;
          return (
            <div>
              {values[name].map((value, index) => (
                <div key={index}>
                  {/* 1つ目のInputは入力必須にする */}
                  {index === 0 ? (
                    <Field
                      name={`${name}[${index}]`}
                      validate={validateArrayInput}
                    />
                  ) : (
                    <>
                      <Field name={`${name}[${index}]`} />
                      <button type="button" onClick={() => remove(index)}>
                        -
                      </button>
                    </>
                  )}
                </div>
              ))}
              <button type="button" onClick={() => push('')}>
                +
              </button>
            </div>
          );
        }}
      </FieldArray>
      <ErrorMessage name={name} />
    </div>
  );
}

export default FieldArrayInput;

File

  • validationチェック(拡張子、ファイルサイズ)は未着手
  • サムネイル画像表示は未着手
FileInput.js
import { ErrorMessage, Field } from 'formik';

import React from 'react';

function FileInput(props) {
  const { label, name, ...rest } = props;
  return (
    <div className="form-control">
      <label htmlFor={name}>{name}</label>
      <Field name={name} {...rest}>
        {({ form }) => {
          const { setFieldValue } = form;
          return (
            <input
              id={name}
              name={name}
              type="file"
              onChange={(event) => {
                setFieldValue(name, event.currentTarget.files[0]);
              }}
            />
          );
        }}
      </Field>
      <ErrorMessage name={name} />
    </div>
  );
}

export default FileInput;

DatePicker

  • react-datepickerを使用
DatePicler.js
import 'react-datepicker/dist/react-datepicker.css';

import { ErrorMessage, Field } from 'formik';

import DateView from 'react-datepicker';
import React from 'react';

function DatePicker(props) {
  const { label, name, ...rest } = props;
  return (
    <div className="form-control">
      <label htmlFor={name}>{label}</label>
      <Field name={name}>
        {({ field, form }) => {
          const { setFieldValue } = form;
          const { value } = field;
          return (
            <DateView
              id={name}
              {...field}
              {...rest}
              selected={value}
              onChange={(val) => setFieldValue(name, val)}
            />
          );
        }}
      </Field>
      <ErrorMessage name={name} />
    </div>
  );
}

export default DatePicker;

Submit

Submit.js
import React from 'react';

function SubmitButton(props) {
  const { formik } = props;
  return (
    <button type="submit" disabled={!formik.isValid}>
      Submit
    </button>
  );
}

export default SubmitButton;

以上です。
修正した方が良い箇所がありましたら教えていただけますと幸いです。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6