※このブログではサーバー運用、技術の検証等の費用のため広告をいれています。
記事が見づらいなどの問題がありましたらContactからお知らせください。

<前のページ
【React + TypeScript】formikで高機能フォームを作成|Part1
次のページ>
【React + TypeScript】formikで高機能フォームを作成|Part3

【React + TypeScript】formikで高機能フォームを作成|Part2

react web開発 フロント開発 TypeScript バリデーション formik フォーム作成

投稿日:2021年6月15日

このエントリーをはてなブックマークに追加
formikというパッケージについて、このパッケージの機能の中身を解説しつつ、使い方について詳しく解説していきます。Part2では実際の環境で使用する用途のコンポーネントを使用して前回のコードをより簡潔にしましょう。

はじめに

この記事について

この記事はPart1の続きになります。

今回はFormik側で実際に使用が推奨されているコンポーネントなどを使ってコードをより簡潔にしていきます。

使用する言語は型付言語のTypeScriptです。

環境について

  • node v10.19.0
  • npm v6.14.4
  • react v17.0.2
  • bashのターミナル

参考URL


実践

属性をまとめて受け取る

前回は各入力要素の対して以下の様に属性を直接指定していました。

<input id="lastName" name="lastName" type="text" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.lastName}></input>

valueに渡す値やonChangeonBlurに渡すイベントハンドラは全ての入力要素に共通した項目のため、コード全体で見るとかなり冗長です。
formikではこのコードの冗長性を簡単に解決できます。

useFormik()の返り値のオブジェクトには以下のような型のメソッドgetFieldProps()が実装されています。

[...]

getFieldProps: (nameOrOptions: any) => FieldInputProps<any>;

[...]

さらにこの中で使われているFieldInputProps<Value>は以下のような型になっています。

export interface FieldInputProps<Value> {
    /** Value of the field */
    value: Value;
    /** Name of the field */
    name: string;
    /** Multiple select? */
    multiple?: boolean;
    /** Is the field checked? */
    checked?: boolean;
    /** Change event handler */
    onChange: FormikHandlers['handleChange'];
    /** Blur event handler */
    onBlur: FormikHandlers['handleBlur'];
}

getFieldProps()は指定したプロパティに対しての入力要素に渡す必要があると予測される要素軍をオブジェクトとして返す関数です。そのオブジェクトをスプレッド構文TSXタグの属性として展開することで、記述がより簡潔になります。

ではやってみましょう!

SignupForm.tsx
import React from "react";
import { useFormik } from "formik";
import * as Yup from "yup";


/**
 * フォームの型
 */
type FormValueType = {
    email: string,
    firstName: string,
    lastName: string
};

/**
 * バリデーションスキーマ
 */
const validationSchema = Yup.object({
    firstName: Yup.string().max(15, "15文字以下で入力してください").required("このフィールドは必須です"),
    lastName: Yup.string().max(15, "15文字以下で入力してください").required("このフィールドは必須です"),
    email: Yup.string().email("正しいメールアドレスを入力してください").required("このフィールドは必須です")
});

/**
     * 送信処理
     * @param values 
     */
 const onSubmit = (values: FormValueType) => {
    alert(JSON.stringify(values, null, 2));
}

/**
 * 登録フォーム
 */
const SignupForm = () => {

    /**
     * フォームの定義
     */
    const formik = useFormik<FormValueType>({
        initialValues: {email : "", firstName:"", lastName:""},
        validationSchema: validationSchema,
        onSubmit: onSubmit
    });

    return (
        <div>
            <form onSubmit={formik.handleSubmit}>
                <label htmlFor="email">メールアドレス</label>
                <input id="email" type="email" {...formik.getFieldProps("email")}></input>
                {formik.touched.email && formik.errors.email ? (
         <div>{formik.errors.email}</div>
       ) : null}
                <label htmlFor="lastName">姓</label>
                <input id="lastName" type="text" {...formik.getFieldProps("lastName")}></input>
                {formik.touched.lastName && formik.errors.lastName ? (
         <div>{formik.errors.lastName}</div>
       ) : null}
                <label htmlFor="firstName">名</label>
                <input id="firstName" type="text" {...formik.getFieldProps("firstName")}></input>
                {formik.touched.firstName && formik.errors.firstName ? (
         <div>{formik.errors.firstName}</div>
       ) : null}
                <button type="submit" onSubmit={() => {formik.handleSubmit();}}>送信</button>
            </form>
        </div>
    )
}

export default SignupForm;

idtypeなどの属性は自身で記述する必要がある点には注意しましょう。これらの属性値はデベロッパに委ねられた値なのでFieldInputProps<Value>にはふくまれません。

Formikコンポーネントの使用

ここでやっと高レベルAPIの使用に入ります。まず利用するコンポーネントは<Formik />です。

<Formik />の実装の型は以下の様になっています。

function Formik<Values extends FormikValues = FormikValues, ExtraProps = {}>(props: FormikConfig<Values> & ExtraProps): JSX.Element;

FormikはGeneric関数であり、更にその型を決めるFormikConfigもGenericの型定義であることがわかります。FormikConfig<Value>の定義は以下の様になっています。

interface FormikConfig<Values> extends FormikSharedConfig {
    component?: React.ComponentType<FormikProps<Values>> | React.ReactNode;
    render?: (props: FormikProps<Values>) => React.ReactNode;
    children?: ((props: FormikProps<Values>) => React.ReactNode) | React.ReactNode;
    initialValues: Values;
    initialStatus?: any;
    initialErrors?: FormikErrors<Values>;
    initialTouched?: FormikTouched<Values>;
    onReset?: (values: Values, formikHelpers: FormikHelpers<Values>) => void;
    onSubmit: (values: Values, formikHelpers: FormikHelpers<Values>) => void | Promise<any>;
    validationSchema?: any | (() => any);
    validate?: (values: Values) => void | object | Promise<FormikErrors<Values>>;
    innerRef?: React.Ref<FormikProps<Values>>;
}

これを見ると<Formik />のpropsの型であるFormikConfig<Value>initialValuesonSubmitが必須のプロパティになっていることがわかります。ここで注意する点はrenderchildrenというオプションプロパティです。この2つはどちらもFormikPropsを受け取りReactNodeを返す関数型の定義です。これはどちらも、実際のフォームを表示するJSXを表すためのプロパティなのですが、公式のドキュメントにあるとおり、renderは非推奨になっているため、かならずchildrenを設定しましょう。
childrenは直接渡しても、そのまま<Formik>の子要素として渡してもOKです!

では実際に書いてみましょう。

SignupForm.tsx
import React from "react";
import { Formik } from "formik";
import * as Yup from "yup";

/**
 * フォームの型
 */
type FormValueType = {
    email: string;
    firstName: string;
    lastName: string;
};

/**
 * バリデーションスキーマ
 */
const validationSchema = Yup.object({
    firstName: Yup.string()
        .max(15, "15文字以下で入力してください")
        .required("このフィールドは必須です"),
    lastName: Yup.string()
        .max(15, "15文字以下で入力してください")
        .required("このフィールドは必須です"),
    email: Yup.string()
        .email("正しいメールアドレスを入力してください")
        .required("このフィールドは必須です"),
});

/**
 * 送信処理
 * @param values
 */
const onSubmit = (values: FormValueType) => {
    alert(JSON.stringify(values, null, 2));
};

/**
 * 登録フォーム
 */
const SignupForm = () => {
    const initialValues: FormValueType = {
        email: "",
        firstName: "",
        lastName: "",
    };
    return (
        <Formik
            initialValues={initialValues}
            validationSchema={validationSchema}
            onSubmit={onSubmit}
        >
            {(formik) => {
                return (
                    <form onSubmit={formik.handleSubmit}>
                        <label htmlFor="email">メールアドレス</label>
                        <input
                            id="email"
                            type="email"
                            {...formik.getFieldProps("email")}
                        />
                        {formik.touched.email && formik.errors.email ? (
                            <div>{formik.errors.email}</div>
                        ) : null}
                        <label htmlFor="lastName">姓</label>
                        <input
                            id="lastName"
                            type="text"
                            {...formik.getFieldProps("lastName")}
                        />
                        {formik.touched.lastName && formik.errors.lastName ? (
                            <div>{formik.errors.lastName}</div>
                        ) : null}

                        <label htmlFor="firstName">名</label>
                        <input
                            id="firstName"
                            type="text"
                            {...formik.getFieldProps("firstName")}
                        />
                        {formik.touched.firstName && formik.errors.firstName ? (
                            <div>{formik.errors.firstName}</div>
                        ) : null}

                        <button type="submit">送信</button>
                    </form>
                );
            }}
        </Formik>
    );
};

export default SignupForm;

<Formik>の子要素には、型の定義通りFormikPropsのオブジェクトを受け取りReactNodeを返す関数を渡します。
ややこしいですが、ここまでuseFormik()で作成していたformikのオブジェクトを受け取ってJSX表現を返す関数を渡せばOKです!(formikのオブジェクトは<Formik>内で自動的に作成されます)

実際に動作を確認するとuseFormik()で作成したときと同じ処理を実装できたことが確認できると思います。

今の状態ではuseFormik()を使用していた頃とコードの複雑さは対して変わっていません。そこで次に<Formik />独自の機能を利用してみましょう。


<Formik />のソースこコードを確認しにいくとわかるのですが、このコンポーネントはFormikContextというReactコンテキストの実装を利用しています。このコンテキストにより、<Formik />以下の子要素は親要素からformikの値にアクセスすることができます。
inputやエラーメッセージについては、このコンテキストを簡単に利用できるコンポーネントが用意されているのでそれを使うことができます。

ではやってみましょう!

./SignupForm.tsx
import React from "react";
import { ErrorMessage, Field, Formik } from "formik";
import * as Yup from "yup";

/**
 * フォームの型
 */
type FormValueType = {
    email: string;
    firstName: string;
    lastName: string;
};

/**
 * バリデーションスキーマ
 */
const validationSchema = Yup.object({
    firstName: Yup.string()
        .max(15, "15文字以下で入力してください")
        .required("このフィールドは必須です"),
    lastName: Yup.string()
        .max(15, "15文字以下で入力してください")
        .required("このフィールドは必須です"),
    email: Yup.string()
        .email("正しいメールアドレスを入力してください")
        .required("このフィールドは必須です"),
});

/**
 * 送信処理
 * @param values
 */
const onSubmit = (values: FormValueType) => {
    alert(JSON.stringify(values, null, 2));
};

/**
 * 登録フォーム
 */
const SignupForm = () => {
    const initialValues: FormValueType = {
        email: "",
        firstName: "",
        lastName: "",
    };
    return (
        <Formik
            initialValues={initialValues}
            validationSchema={validationSchema}
            onSubmit={onSubmit}
        >
            {(formik) => {
                return (
                    <form onSubmit={formik.handleSubmit}>
                        <label htmlFor="email">メールアドレス</label>
                        <Field name="email" type="text" />
                        <ErrorMessage name="email" />
                        <label htmlFor="lastName">姓</label>
                        <Field name="lastName" type="text" />
                        <ErrorMessage name="firstName" />
                        <label htmlFor="firstName">名</label>
                        <Field name="firstName" type="firstName" />
                        <ErrorMessage name="firstName" />
                        <button type="submit">送信</button>
                    </form>
                );
            }}
        </Formik>
    );
};

export default SignupForm;

ここでは<Field /><ErrorMessage />を利用しました。
大分簡潔になりましたね!

このエントリーをはてなブックマークに追加

<前のページ
【React + TypeScript】formikで高機能フォームを作成|Part1
次のページ>
【React + TypeScript】formikで高機能フォームを作成|Part3

関連記事

記事へのコメント