はじめに
先日react-redux-from
からFormik
に切り換えたのですが、普段使いのUI Frameworkはreact-bootstrapなんです。Formik
の公式を見ると、未対応じゃないですか。reactstrapっていつの間にかBootstrap4
に対応していたんですね…。知らなかった…。ドキュメントとか不親切だからreact-bootstrap
の方が良いと思うんですけどね。
仕方がないので
自分でコンポーネントを作ることにしました。
希望する機能としては以下の通り。
- Input Groupに対応
-
<Field name="name" component={Text} />
よりも<Text name="name" />
のような使い方がしたい。 -
onChange
onBlur
をハンドリング
とりあえず作ってみる
import React from 'react';
import { InputGroup, Form } from 'react-bootstrap';
import { Field } from 'formik';
export const Text = ({
prependText,
appendText,
name,
...props
}) => (
<Field name={name} {...props}>
{({
field,
meta,
}) => {
return (
<>
<InputGroup>
{prependText && (
<InputGroup.Prepend>
<InputGroup.Text> {prependText} </InputGroup.Text>
</InputGroup.Prepend>
)}
<Form.Control
type="text"
{...props}
{...field}
isInvalid={meta.touched && meta.error}
/>
{appendText && (
<InputGroup.Append>
<InputGroup.Text> {appendText} </InputGroup.Text>
</InputGroup.Append>
)}
{meta.touched && meta.error && (
<Form.Control.Feedback type="invalid">
{meta.error}
</Form.Control.Feedback>
)}
</InputGroup>
</>
);
}}
</Field>
);
上記、一件良さそうに見えます。しかも公式が推奨している書き方だし。でも、InputGroup
を使うとコンポーネントの角が出てイケてないんです。こんな風になる。
対策として以下の書き方をします。
<InputGroup>
{prependText && (
<InputGroup.Prepend>
<InputGroup.Text> {prependText} </InputGroup.Text>
</InputGroup.Prepend>
)}
<Form.Control
type="text"
{...props}
{...field}
onChange={handleChange}
onBlur={handleBlur}
isInvalid={meta.touched && meta.error}
/>
{appendText && (
<InputGroup.Append>
<InputGroup.Text> {appendText} </InputGroup.Text>
</InputGroup.Append>
)}
</InputGroup>
{meta.touched && meta.error && (
<InputGroup>
<div className="invalid-feedback d-block">{meta.error}</div>
</InputGroup>
)}
Form
コンポーネントを使うのを止め、別のInputGroup
に出して className="d-block"
を使ってinvalid-feedback
クラスの display:none
を上書きします。これにより理想の出力となります。公式も修正した方が良いと思うんですけどね。
使うときはこんな感じ
<Text name="zipcode" type="tel" prependText="〒" />
onChange onBlurに対応させる
<Field />
はデフォルトで onChange onBlurをフックしちゃうので一手間かけます。
export const Text = ({
onChange,
onBlur,
/* 省略 */
}) => (
<Field name={name} {...props}>
{({
field,
meta,
}) => {
const handleChange = event => {
if (onChange) {
onChange(field.name, event.target.value);
}
field.onChange(field.name)(event);
};
const handleBlur = event => {
if (onBlur) {
onBlur(field.name, event.target.value);
}
field.onBlur(field.name)(event);
};
return (
/* 省略 */
<Form.Control
type="text"
{...props}
{...field}
onChange={handleChange}
onBlur={handleBlur}
isInvalid={meta.touched && meta.error}/>
/* 省略 */
);
}}
</Field>
);
これにより
import { Text } from '/path/to/inputFiled.js';
const handleChange = (fieldName,value ) => {
};
<Text onChange={(fieldName,value)=>handleChange(fieldName,value)} />
のような使い方ができます。まあ、onChange
が無くても Yup
の拡張で何とかなるんですが。
発展
他にも
- コンポーネント内の
handleBlur
に記述を追加する事によって入力値の変換が可能になる。<Katakana />
コンポーネントとかが作成できます。handleChange
には入れない方が良いです。 -
react-number-formatと組み合わせて
<Number />
<Dec />
等が作成可能 -
react-datetime と組み合わせて
<Date />
勢いに乗って <Radio />
<Checkbox />
<Select />
なんかも作成しました。 <Number />
と<Date />
は癖があって稼働までに苦労したので機会&&反応があれば別途記述したいですね。
最後に
今回の記述方法は他のUI FrameworkをFormik
に対応させる場合にも応用できると思います。