redux-form (1) - Simple Form Example
redux-form (2) - Synchronous Validation Example
redux-form (3) - Field-Level Validation Example
redux-form (4) - Submit Validation Example
redux-form (5) - Initialize From State
redux-form (6) - ユーザ登録
ReactでForm componentを作るときに、とても便利な**redux-form**の説明です。
redux-formの概説についてはまず以下の記事を参考にしてください。
redux-form (1) - Simple Form Example
今回は少し実用を意識して、ユーザ登録を実装します。UIコンポネントを作成するためにantdを使います。
React UI library の antd について (1) - Button
ユーザ登録
画面イメージ
画面イメージです。antdを使っているので、簡単にアイコンで装飾できます。
環境設定
まずは開発環境を構築します。
yarn create react-app antd-demo
cd antd-demo
yarn add redux react-redux redux-form redux-logger antd
念のため、package.jsonを掲載しておきます。
{
"name": "antd-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"antd": "^3.20.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.1.0",
"react-scripts": "3.0.1",
"redux": "^4.0.1",
"redux-form": "^8.2.4",
"redux-logger": "^3.0.6"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
ソースコード
ユーザの配列を保持する Redux state を定義します。ユーザを追加するだけの簡単なものにします。
const users = (state = [], action) => {
switch (action.type) {
case 'ADD_USER': // *** userを追加
return [
...state, // *** 分割代入、stateに追加
{
email: action.user.email,
name: action.user.name,
password: action.user.password
}
]
default:
return state
}
}
export default users
これもユーザを追加するactionだけを定義します。
export const addUser = user => ({
type: 'ADD_USER',
user: user
})
index.jsです。ユーザ登録のみの簡単な機能しかありませんが、redux-logger と redux-formを組み合わせているので、少し複雑かもしれません。
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { reducer as reduxFormReducer } from 'redux-form'
import logger from 'redux-logger'
import Register from './Register'
import users from './reducers'
import { addUser } from './actions'
const dest = document.getElementById('root')
const reducer = combineReducers({
users,
form: reduxFormReducer
})
const store = createStore(
reducer,
applyMiddleware(logger)
)
const showResults = values =>
new Promise(resolve => {
setTimeout(() => {
// simulate server latency
console.log(`submitted:${JSON.stringify(values, null, 2)}`)
store.dispatch(addUser(values));
resolve()
}, 500)
})
// redux-formを使っているのでProviderは必要
let render = () => {
ReactDOM.hydrate(
<Provider store={store}>
<Register onSubmit={showResults} />
</Provider>,
dest
)
}
render()
ユーザ登録画面の構築には、antdとredux-formを組み合わせて作っているので、少し複雑です。
- Register componentの大枠はantdのFormを利用しています。
- Formの子要素にはredux-formのField を利用しています。
- 更にFieldのcomponent属性にantdのInput(で作られたcomponent)を利用しています。
antdとredux-formを組み合わせについては、以下の記事も参照ください。
React UI library の antd について (3) - redux-form
redux-formによるvalidateはシンプルなのでいいですね。
import React from 'react';
import PropTypes from 'prop-types';
import { Form, Icon, Button } from 'antd';
import { Field, reduxForm } from 'redux-form';
import renderInput from './input';
import 'antd/dist/antd.css';
import './style.css';
const RegisterForm = props => {
const { handleSubmit, pristine, submitting, message } = props;
return (
<Form onSubmit={handleSubmit} className="form-register-containers">
<h1 className="center">
ユーザ登録
</h1>
<Field
name="email"
hasFeedback
component={renderInput}
disabled={submitting}
label="メールアドレス"
placeholder="メールアドレス"
prefix={<Icon type="mail" style={{ color: 'rgba(0,0,0,.25)' }} />}
/>
<Field
hasFeedback
type="password"
name="password"
component={renderInput}
disabled={submitting}
label="パスワード"
placeholder="パスワード"
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
/>
<Field
hasFeedback
type="password"
name="confirmPassword"
component={renderInput}
disabled={submitting}
label="確認用パスワード"
placeholder="確認用パスワード"
prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
/>
<Field
hasFeedback
name="name"
component={renderInput}
disabled={submitting}
label="お名前"
placeholder="お名前"
prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
/>
<Form.Item className="center">
<Button
type="primary"
htmlType="submit"
className="btn-submit"
disabled={pristine || submitting}
>
ユーザ登録
</Button>
</Form.Item>
{!!message && <p className="caption-invalid">{message}</p>}
</Form>
);
};
// redux-formのvalidate
const validate = values => {
const errors = {};
if (!values.email) {
errors.email = 'Required';
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)
) {
errors.email = 'Invalid email address';
}
if (!values.password) {
errors.password = "Password can't be blank";
}
if (!values.confirmPassword) {
errors.confirmPassword = "Confirm password can't be blank";
}
if (
values.password &&
values.confirmPassword &&
values.password !== values.confirmPassword
) {
errors.confirmPassword = "Confirm password didn't match";
}
if (!values.name) {
errors.name = "Name can't be blank";
}
return errors;
};
RegisterForm.propTypes = {
pristine: PropTypes.bool,
message: PropTypes.string,
submitting: PropTypes.bool,
handleSubmit: PropTypes.func,
};
export default reduxForm({
form: 'register-form',
validate,
})(RegisterForm);
以下がrenderInput componentのソースです。propsを展開して、inputやrestを取り出していることに注目してください。そのままInput componentに展開されます。特に**(1)input object (redux-form) はvalueを含んでおり、(2)Input (antd component) のvalue属性の値としてそのまま適用されます。このvalueはactionによりRedux stateにbindされています。**またrestにはprefixが含まれており、そのままアイコン情報が展開されます。
(1) Redux-form Fieldのinput object
(2) Antd Input component
import React from 'react';
import { Form, Input } from 'antd';
const newComponent = props => {
const { input, meta, hasFeedback, label, ...rest } = props;
const hasError = meta.touched && meta.invalid;
return (
<Form.Item
label={label}
help={hasError && meta.error}
hasFeedback={hasFeedback && hasError}
validateStatus={hasError ? 'error' : 'success'}
>
<Input {...input} {...rest} />
</Form.Item>
);
};
export default newComponent;
実行結果
今回は以上です。