先日、「React Formikの入門編」の記事を投稿しました。
その記事をもとにformの各部品(input、textarea、select)などをcomponentとして分けたので共有します。
※実際にアプリケーションを開発する際はこのようにcomponent分けをするのではないでしょうか。
FormContainer
- form画面の親コンポーネント
-
initialValues
、validationSchema
、onSubmit
を定義 - 呼び出し先に送る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;
以上です。
修正した方が良い箇所がありましたら教えていただけますと幸いです。