
Reactのフォーム用ライブラリ「Formik」でフォームを華麗に扱う方法を紹介します。
はじめに
Reactでフォームを扱う場合、ライブラリを使わないとソースコードが煩雑になってしまいます。React/Reduxの環境でポピュラーなフォーム用のライブラリといえば「Redux Form」ですが、Redux自体は必須というわけではなく、Reactのみでフロントエンドを構築する場合もあるため、Reduxを前提としたフォーム用ライブラリを使うと、React/Reduxで構築したアプリケーションをReactのみに書き直した場合などにコンポーネントの再利用性を低下させてしまいます。そこで、Reduxとは関係のないReactのフォーム用ライブラリとして「Formik」という選択肢が出てきます。
今回は、「Formik」を使って、Reactのフォームを扱う方法を紹介します。
Formikとは?
「Formik」とは、単独で利用可能なReactのフォーム用ライブラリです。
前提
以下の準備が完了している必要があります。
- NodeJSがインストールされていること
- create-react-appがインストールされていること
細かいバージョンは「環境」を参照してください。
FormikでReactのフォームを作成する
ベースを作る
まずは、Reactのプロジェクトを作成し、必要なパッケージをインストールします。
$ npx create-react-app formik-sample
$ cd formik-sample/
$ rm src/App.css
$ rm src/App.test.js
$ rm src/logo.svg
$ touch src/MyForm.js
$ yarn add formik yup bootstrap reactstrap
$ tree -aI 'node_modules|.git'
.
├── .gitignore
├── README.md
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── src
│ ├── App.js
│ ├── MyForm.js
│ ├── index.css
│ ├── index.js
│ └── serviceWorker.js
└── yarn.lock
フォームを実装する
入力チェック付きのフォームを実装しましょう。
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
index.css
body {
text-align: center;
font-size: 1.5rem;
padding: 3%;
}
App.js
import React from 'react';
import MyForm from './MyForm';
const App = () => (
<>
<MyForm />
</>
);
export default App;
MyForm.js
import React from 'react';
import {
Button,
Form,
FormGroup,
Label,
Input as ReactstrapInput,
FormFeedback,
} from 'reactstrap';
import { withFormik, ErrorMessage, Field } from 'formik';
import * as yup from 'yup';
const Input = ({ name, ...others }) => (
<Field
name={name}
render={({ field }) => <ReactstrapInput {...field} {...others} />}
/>
);
const ErrorFormFeedback = ({ name }) => (
<ErrorMessage
name={name}
component={({ children }) => <FormFeedback>{children}</FormFeedback>}
/>
);
const ErrorInnerMessage = ({ name }) => (
<ErrorMessage
name={name}
component={({ children }) => (
<span className="text-danger" style={{ fontSize: '1.2rem' }}>
{children}
</span>
)}
/>
);
const MyForm = ({
handleSubmit,
handleReset,
isSubmitting,
dirty,
errors,
touched,
}) => (
<div className="mx-auto col-8">
<h2>My Form</h2>
<Form className="text-left" onSubmit={handleSubmit}>
<FormGroup className="mb-2">
<Label for="myEmail">Email</Label>
<Input
type="email"
name="email"
id="myEmail"
placeholder="Enter email"
valid={dirty && !errors.email}
invalid={touched.email && !!errors.email}
/>
<ErrorFormFeedback name="email" />
</FormGroup>
<FormGroup className="mb-2">
<Label for="myUsername">Username</Label>
<Input
type="text"
name="username"
id="myUsername"
placeholder="Enter username"
valid={dirty && !errors.username}
invalid={touched.username && !!errors.username}
/>
<ErrorFormFeedback name="username" />
</FormGroup>
<FormGroup className="mb-2">
<Label for="myPassword">Password</Label>
<Input
type="password"
name="password"
id="myPassword"
placeholder="Enter password"
valid={dirty && !errors.password}
invalid={touched.password && !!errors.password}
/>
<ErrorFormFeedback name="password" />
</FormGroup>
<FormGroup className="mb-2" tag="fieldset">
<legend>Gender</legend>
<FormGroup inline check>
<Label check>
<Input type="radio" name="gender" value="male" />
male
</Label>
</FormGroup>
<FormGroup inline check>
<Label check>
<Input type="radio" name="gender" value="female" />
female
</Label>
</FormGroup>
<span className="ml-3">
<ErrorInnerMessage name="gender" />
</span>
</FormGroup>
<FormGroup check className="mb-2">
<Input type="checkbox" name="isAccepted" id="myCheck" />
<Label for="myCheck" check>
Accept
</Label>
<span className="ml-3">
<ErrorInnerMessage name="isAccepted" />
</span>
</FormGroup>
<div className="d-flex justify-content-center">
<span className="p-2">
<Button
type="button"
outline
color="secondary"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</Button>
</span>
<span className="p-2">
<Button type="submit" outline color="primary" disabled={isSubmitting}>
Submit
</Button>
</span>
</div>
</Form>
</div>
);
const MyEnhancedForm = withFormik({
mapPropsToValues: () => ({
username: '',
email: '',
password: '',
gender: '',
isAccepted: false,
}),
handleSubmit: (values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 3000);
},
validationSchema: yup.object().shape({
email: yup
.string()
.email('Enter a correct email ')
.required('Enter an email'),
username: yup
.string()
.min(3, 'A username must contain more than 3 characters')
.required('Enter a username'),
password: yup
.string()
.min(8, 'A username must contain more than 8 characters')
.required('Enter a password'),
gender: yup
.string()
.oneOf(['male', 'female'])
.required('Check a gender'),
isAccepted: yup.boolean().oneOf([true], 'Must accept terms and conditions'),
}),
})(MyForm);
export default MyEnhancedForm;
Formikの実装方法は、Formikコンポーネントでそのままフォームを実装するシンプルな方法と、withFormikコンポーネントでフォームのコンポーネントをラップするHOC(higher-order component)の方法があります。今回は実際に使うことが多い後者のHOCの方法で実装しています。
動作確認
それでは、アプリケーションを起動し、フォームの入力チェックが正しく動くか確認しましょう。
$ yarn start
入力チェックが正しく行われるか確認します。
正しいデータが入力されたこを確認します。
「Submit」ボタンをクリックして、入力したデータが表示されることを確認します。
OKですね。
最後に
いかがでしたか?これでReduxに依存すること無く、「Formik」でReactのフォームを華麗に扱うことができるようになったのではないでしょうか。それでは。
環境
- NodeJS: v11.13.0
- create-react-app: 2.1.8
- bootstrap: 4.3.1
- reactstrap: 8.0.0
- formik: 1.5.2
- yup: 0.27.0