投稿日:2021年6月15日
formikというパッケージについて、このパッケージの機能の中身を解説しつつ、使い方について詳しく解説していきます。Part2では実際の環境で使用する用途のコンポーネントを使用して前回のコードをより簡潔にしましょう。
前回は各入力要素の対して以下の様に属性を直接指定していました。
<input id="lastName" name="lastName" type="text" onChange={formik.handleChange} onBlur={formik.handleBlur} value={formik.values.lastName}></input>
valueに渡す値やonChange、onBlurに渡すイベントハンドラは全ての入力要素に共通した項目のため、コード全体で見るとかなり冗長です。
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タグの属性として展開することで、記述がより簡潔になります。
ではやってみましょう!
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;
idやtypeなどの属性は自身で記述する必要がある点には注意しましょう。これらの属性値はデベロッパに委ねられた値なのでFieldInputProps<Value>にはふくまれません。
ここでやっと高レベル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>はinitialValuesとonSubmitが必須のプロパティになっていることがわかります。ここで注意する点はrenderとchildrenというオプションプロパティです。この2つはどちらもFormikPropsを受け取りReactNodeを返す関数型の定義です。これはどちらも、実際のフォームを表示するJSXを表すためのプロパティなのですが、公式のドキュメントにあるとおり、renderは非推奨になっているため、かならずchildrenを設定しましょう。
childrenは直接渡しても、そのまま<Formik>の子要素として渡してもOKです!
では実際に書いてみましょう。
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やエラーメッセージについては、このコンテキストを簡単に利用できるコンポーネントが用意されているのでそれを使うことができます。
ではやってみましょう!
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 />を利用しました。
大分簡潔になりましたね!