Reactで（少し）動的なFormを作る


## やりたいこと

* 上段のプルダウンで値を選ぶと、下段のプルダウンで選べる要素が変わる（制限される）
* ModalでFormを出して値を任意の数追加する

## メモ

* なぜかFormikのvalidateField()の実行がエラーとなる（issue上がってない・・・）

## 完成イメージ

下記のような感じ。エリアで関西とかを選ぶと、大阪、兵庫とかしか選べなくなる。
趣味はいくつあるかわからないので、必要数入れてもらう（うらでは配列とかで持つ）。
コードは[githubにあげてみた](https://github.com/eizaburo/react-dynamic-form-sample)ので、動作確認してからいじったほうがわかりやすいかも。

![スクリーンショット 2019-12-19 7.11.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55188/eac483aa-6b6e-581f-80eb-3bce9c36490a.png "スクリーンショット 2019-12-19 7.11.12.png")



## 実装

正解があるわけではないが、とりあえず下記のようにしてみた。


```js:App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

import { Formik, Form as FormikForm, validateYupSchema } from 'formik';
import * as Yup from 'yup';
import {
    Form,
    FormGroup,
    Label,
    Input,
    FormFeedback,
    Button,
    Modal,
    ModalHeader,
    ModalBody,
    ModalFooter
} from 'reactstrap';


const areas = [
    { name: '選択してください', value: '選択してください' },
    { name: '関西', value: '関西' },
    { name: '関東', value: '関東' },
    { name: 'その他', value: 'その他' },
];

const prefs = [
    { area: '選択してください', name: '選択してください', value: '選択してください' },
    { area: '関東', name: '東京', value: '東京' },
    { area: '関東', name: '千葉', value: '千葉' },
    { area: '関西', name: '大阪', value: '大阪' },
    { area: '関西', name: '兵庫', value: '兵庫' },
    { area: 'その他', name: '北海道', value: '北海道' },
]

class App extends React.Component {

    state = {
        modalOpen: false,
        hobbies: [],
    }

    handleSubmit1 = (values) => {
        alert(JSON.stringify(values));
    }

    handleSubmit2 = (values) => {
        alert(JSON.stringify(values));
    }

    handleHobbyAdd = async (hobby) => {
        const _hobbies = [...this.state.hobbies];
        _hobbies.push(hobby);
        await this.setState({ hobbies: _hobbies });
        this.handleModalClose();
    }

    handleModalClose = () => {
        this.setState({
            modalOpen: false,
        });
    }

    handleModalOpen = () => {
        this.setState({
            modalOpen: true,
        });
    }

    handleHobbyDelete = (index) => {
        // alert(index);
        const _hobbies = [...this.state.hobbies];
        _hobbies.splice(index, 1);
        this.setState({ hobbies: _hobbies });
    }

    render() {
        return (
            <div className="container">
                <h3 className="my-5 text-center">動的フォームテスト</h3>
                <h4 className="my-4">エリア選択フォーム</h4>
                <Formik
                    initialValues={{ area: '選択してください', pref: '選択してください' }}
                    onSubmit={(values) => this.handleSubmit1(values)}
                    validationSchema={Yup.object().shape({
                        area: Yup.string().notOneOf(['選択してください']),
                        pref: Yup.string().notOneOf(['選択してください']),
                    })}
                >
                    {
                        ({ handleSubmit, handleChange, handleBlur, values, errors, touched }) => (
                            <Form onSubmit={handleSubmit}>
                                <FormGroup>
                                    <Label>エリア</Label>
                                    <Input
                                        type="select"
                                        name="area"
                                        value={values.area}
                                        onChange={handleChange}
                                        onBlur={handleBlur}
                                        invalid={Boolean(touched.area && errors.area)}
                                    >
                                        {
                                            areas.map((area, index) => (
                                                <option value={area.value} key={index}>{area.name}</option>
                                            ))
                                        }
                                    </Input>
                                    <FormFeedback>{errors.area}</FormFeedback>
                                </FormGroup>
                                <FormGroup>
                                    <Label>都道府県</Label>
                                    <Input
                                        type="select"
                                        name="pref"
                                        value={values.pref}
                                        onChange={handleChange}
                                        onBlur={handleBlur}
                                        invalid={Boolean(touched.pref && errors.pref)}
                                    >
                                        {
                                            (prefs.filter(pref => pref.area === values.area || pref.area === '選択してください')).map((pref, index) => (
                                                <option value={pref.value} key={index}>{pref.name}</option>
                                            ))
                                        }
                                    </Input>
                                    <FormFeedback>{errors.pref}</FormFeedback>
                                </FormGroup>
                                <div>
                                    <Button type="submit" color="primary">登録</Button>
                                </div>
                            </Form>
                        )
                    }
                </Formik>
                <h4 className="my-4">趣味登録フォーム</h4>
                <Formik
                    enableReinitialize
                    initialValues={{ name: 'hoge', hobby: '', hobbies: this.state.hobbies }}
                    onSubmit={values => this.handleSubmit2(values)}
                    validationSchema={Yup.object().shape({
                        name: Yup.string().required(),
                        hobbies: Yup.array().min(1),
                    })}
                >
                    {
                        ({ handleSubmit, handleBlur, handleChange, values, errors, touched, validateField, setFieldValue }) => (
                            <Form onSubmit={handleSubmit}>
                                <FormGroup>
                                    <label>氏名</label>
                                    <Input
                                        type="text"
                                        name="name"
                                        value={values.name}
                                        onChange={handleChange}
                                        onBlur={handleBlur}
                                        invalid={Boolean(touched.name && errors.name)}
                                    />
                                    <FormFeedback>
                                        {errors.name}
                                    </FormFeedback>
                                </FormGroup>
                                <FormGroup>
                                    <legend className="col-form-label">趣味</legend>
                                    <div>
                                        <Button type="button" size="sm" color="success" onClick={() => {
                                            // setFieldValue('hobby', '');
                                            this.handleModalOpen();
                                        }}>趣味を追加 +</Button>
                                        {
                                            this.state.hobbies.map((hobby, index) => (
                                                <div className="row container" key={index}>
                                                    <Input
                                                        type="text"
                                                        value={hobby}
                                                        disabled
                                                        className="col-11 my-3"
                                                    />
                                                    <Button
                                                        className="col-1 my-3 px-1"
                                                        type="button"
                                                        onClick={() => this.handleHobbyDelete(index)}
                                                    >X</Button>
                                                </div>
                                            ))
                                        }
                                    </div>
                                    <p className="text-danger"><small>{touched.hobbies && errors.hobbies ? errors.hobbies : null}</small></p>
                                </FormGroup>

                                <Modal isOpen={this.state.modalOpen}>
                                    <ModalHeader toggle={this.handleModalClose}>趣味</ModalHeader>
                                    <ModalBody>
                                        <FormGroup>
                                            <Label>趣味名</Label>
                                            <Input
                                                type="text"
                                                name="hobby"
                                                value={values.hobby}
                                                onChange={handleChange}
                                                onBlur={handleBlur}
                                                invalid={Boolean(touched.hobby && errors.hobby)}
                                            />
                                            <FormFeedback>{errors.hobby}</FormFeedback>
                                        </FormGroup>
                                    </ModalBody>
                                    <ModalFooter>
                                        <Button type="button" color="info" onClick={() => {
                                            // validateField('name');
                                            // validateField('hobby');
                                            // なぜかvalidateFieldがエラーになる。。。
                                            if (values.hobby === '') {
                                                alert("必須です。")
                                                return;
                                            }
                                            this.handleHobbyAdd(values.hobby);
                                        }}>登録</Button>
                                        <Button type="button" color="secondary" onClick={this.handleModalClose}>キャンセル</Button>
                                    </ModalFooter>
                                </Modal>

                                <div>
                                    <Button type="submit" type="submit" color="primary">登録</Button>
                                </div>
                            </Form>
                        )
                    }
                </Formik>
            </div>
        );
    }
}

export default App;
```