React のコンテクストはグローバル的にロジックと状態を呼び出す機能です。コンテクストは状況を表す語であり、React のコンテクストも語源同様にページやサイトの状況を表現する時に用いるとすっきりしたコードになりやすいです。
コンテクスト – React
コンテクストを用いればページを構成するコンポーネントが多段構造になっていたとしても、コンポーネント全てに状態とロジックを表現する props をリレーさせる必要なく最上部と末端で状態を持つ一つのコンテクストを参照できます。
react-form-hook は React 上でフォームのバリデーションを行うためのライブラリです。
ホーム | React Hook Form – Simple React forms validation
【React】React Hook Form と yup でフォームのバリデーションを作る – 株式会社シーポイントラボ | 浜松のシステム・RTK-GNSS開発
これを用いると次の様な動的バリデーション機能を React 上で整理されたコードで実装できます。
この react-form-hook にはコンテクスト形式が用意されており、これを利用することで動的バリデーション機能を含んだフォームのフィールド部と各フィールドを管理する部分を完全に分離できます。これにより、エラー通知や値の受け渡しなどのリレーが解消されてかなりコードをすっきりさせられます。
API ドキュメント | React Hook Form – Simple React forms validation
これは例えば次の様に作れます。次のコードは Material UI というデザインコンポーネントライブラリの input[type=text] を表現するコンポーネントである TextField を使った例となります。Material UI が登場するのは末端のみなので他のデザインコンポーネントでも応用できます。
/** Page.tsx */
// バリデーションルール定義
const schema = yup.object().shape({
name: yup.string().required().max(255).label('名前'),
email: yup.string().required().email().label('メールアドレス'),
});
export const Page: React.FC = () => {
// あるページを表現するコンポーネントの中でバリデータを準備
const formMethods = useForm<FormInputTypes>({ resolver: yupResolver(schema)});
// バリデータを元にフォームの内容を送信
const onSubmit: ButtonProps['onClick'] = formMethods.handleSubmit(() => {
// submitAction は与えられたオブジェクトを元にリクエストを送信する関数です。
Object.keys(formMethods.errors).length === 0 && submitAction(formMethods.getValues());
});
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
{/* コンテクストを使うためのプロバイダ定義 */}
<FormProvider {...formMethods}>
<form onSubmit={formMethods.handleSubmit(onSubmit)}>
<AppFormFields />
</form>
</FormProvider>
<Grid item xs={12}>
<FormControl>
<Button onClick={onSubmit}> 送信 </Button>
</FormControl>
</Grid>
</div>
);
}
/** AppFormFields.tsx */
type FormFieldInputs = {
name: string;
}
// input 要素のなどを纏めて記載するフィールドコンポーネント
// こうすると作成、更新などの同じフォームフィールドを使う時に共通化できて便利
//
// コンテクスト抜きの場合、このコンポーネントの各フィールドについて直にエラーの受け渡しと値の変化の通知を作る必要があって
// 巨大なコードになりやすいです。
export const AppFormFields:React.FC = () => {
// ここではフィールドの列挙のみします
return (
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(4, 1fr)',
gap: '10px',
}}
>
{/* コンテクストを使ったことでerror、value、表示メッセージのリレー的な受け渡しをせずに済みます */}
<AppFormTextField label="名前" name="name" />
<AppFormTextField label="メールアドレス" name="email" />
</div>
);
}
/** AppFormTextField.tsx */
import { TextFieldProps } from '@material-ui/core';
import TextField from '@material-ui/core/TextField';
import React from 'react';
import { useFormContext } from 'react-hook-form';
type AppFormTextFieldProps = TextFieldProps & {
name: string;
};
export const AppFormTextField: React.FC<AppFormTextFieldProps> = (props) => {
// react-form-hook のコンテクストを使用します
const { errors, register } = useFormContext();
// MaterialUI のエラー表示と react-form-hook のエラー通知を接続
return (
<TextField error={!!errors[props.name]} helperText={errors[props.name]?.message} inputRef={register} {...props} />
);
};
こんな感じでフォームを表現するコンポーネントにフォームの状況を表現するコンテクストを使うとすっきりしたコードを書きやすいです。