Help us understand the problem. What is going on with this article?

Reactで(少し)動的なFormを作る

やりたいこと

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

メモ

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

完成イメージ

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

スクリーンショット 2019-12-19 7.11.12.png

実装

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

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;
zaburo
こんにちは。自分用のメモをだらだら公開しています。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした