Skip to content

Instantly share code, notes, and snippets.

@shinaisan
Last active October 31, 2017 14:24
Show Gist options
  • Save shinaisan/9b549dd595c8b766ecf5accaab7d61de to your computer and use it in GitHub Desktop.
Save shinaisan/9b549dd595c8b766ecf5accaab7d61de to your computer and use it in GitHub Desktop.
Redux Form - Wizard Form Example
import React from 'react';
import WizardForm from './WizardForm';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from './reducer';
import "bootstrap/dist/css/bootstrap.css";
const store = createStore(reducer);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {debug: ''};
}
handleSubmit(values) {
const self = this;
self.setState({debug: JSON.stringify(values)});
}
render() {
const onSubmit = this.handleSubmit.bind(this);
return (
<Provider store={store}>
<div>
<WizardForm onSubmit={onSubmit} />
<div>
<pre>{this.state.debug}</pre>
</div>
</div>
</Provider>
);
}
}
export default App;
#!/bin/bash
NAME=redux-form-wizard
echo -n "This script runs create-react-app $NAME. Proceed? (Y/N) "
read YN
if [ x$YN != xY ]
then
echo "Bye."
exit
fi
# Delete this line only if you are sure what is done from this line on.
exit
create-react-app $NAME
cd $NAME
cp -v ../package.json .
cd src
rm -f App.* logo.svg
cp -v ../../*.js .
cd ..
yarn install
echo 'To launch the dev server, run `yarn run start`.'
{
"name": "redux-form-selecting-form-values",
"version": "0.1.0",
"private": true,
"dependencies": {
"bootstrap": "^3.3.7",
"immutable": "^3.8.2",
"react": "^16.0.0",
"react-bootstrap": "^0.31.5",
"react-dom": "^16.0.0",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"redux-form": "^7.1.2",
"redux-immutablejs": "^0.0.8"
},
"devDependencies": {
"react-scripts": "1.0.14"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
const rootReducer = combineReducers({
form: formReducer
});
export default rootReducer;
import React from 'react'
const renderField = ({ input, label, type, meta: { touched, error } }) => (
<div>
<label>{label}</label>
<div>
<input {...input} placeholder={label} type={type} />
{touched && error && <span>{error}</span>}
</div>
</div>
)
export default renderField
const validate = values => {
const errors = {}
if (!values.firstName) {
errors.firstName = 'Required'
}
if (!values.lastName) {
errors.lastName = 'Required'
}
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.sex) {
errors.sex = 'Required'
}
if (!values.favoriteColor) {
errors.favoriteColor = 'Required'
}
return errors
}
export default validate
const warn = values => {
// IMPORTANT: values is an Immutable.Map here!
const errors = {}
if (values.get('username') && /[^a-zA-Z0-9 ]/i.test(values.get('username'))) {
errors.username = 'Only alphanumeric characters'
}
if (values.get('email') && /.+@aol\.com/.test(values.get('email'))) {
errors.email = 'Really? You still use AOL for your email?'
}
if (values.get('age') && values.get('age') > 65) {
errors.age = 'You might be too old for this'
}
return errors
}
export default warn
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import WizardFormFirstPage from './WizardFormFirstPage'
import WizardFormSecondPage from './WizardFormSecondPage'
import WizardFormThirdPage from './WizardFormThirdPage'
class WizardForm extends Component {
constructor(props) {
super(props)
this.nextPage = this.nextPage.bind(this)
this.previousPage = this.previousPage.bind(this)
this.state = {
page: 1
}
}
nextPage() {
this.setState({ page: this.state.page + 1 })
}
previousPage() {
this.setState({ page: this.state.page - 1 })
}
render() {
const { onSubmit } = this.props
const { page } = this.state
return (
<div>
{page === 1 && <WizardFormFirstPage onSubmit={this.nextPage} />}
{page === 2 && (
<WizardFormSecondPage
previousPage={this.previousPage}
onSubmit={this.nextPage}
/>
)}
{page === 3 && (
<WizardFormThirdPage
previousPage={this.previousPage}
onSubmit={onSubmit}
/>
)}
</div>
)
}
}
WizardForm.propTypes = {
onSubmit: PropTypes.func.isRequired
}
export default WizardForm
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import validate from './validate'
import renderField from './renderField'
import {
Form,
Button
} from 'react-bootstrap'
const WizardFormFirstPage = props => {
const { handleSubmit } = props
return (
<Form onSubmit={handleSubmit}>
<Field
name="firstName"
type="text"
component={renderField}
label="First Name"
/>
<Field
name="lastName"
type="text"
component={renderField}
label="Last Name"
/>
<div>
<Button type="submit" bsStyle="primary" className="next">
Next
</Button>
</div>
</Form>
)
}
export default reduxForm({
form: 'wizard', // <------ same form name
destroyOnUnmount: false, // <------ preserve form data
forceUnregisterOnUnmount: true, // <------ unregister fields on unmount
validate
})(WizardFormFirstPage)
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import validate from './validate'
import renderField from './renderField'
import {
Form, FormGroup,
Button
} from 'react-bootstrap'
const renderError = ({ meta: { touched, error } }) =>
touched && error ? <span>{error}</span> : false
const WizardFormSecondPage = props => {
const { handleSubmit, previousPage } = props
return (
<Form onSubmit={handleSubmit}>
<Field name="email" type="email" component={renderField} label="Email" />
<FormGroup>
<label>Sex</label>
<div>
<label>
<Field
name="sex"
component="input"
type="radio"
value="male"
/>{' '}
Male
</label>
<label>
<Field
name="sex"
component="input"
type="radio"
value="female"
/>{' '}
Female
</label>
<Field name="sex" component={renderError} />
</div>
</FormGroup>
<div>
<Button type="button"
bsStyle="default" className="previous" onClick={previousPage}>
Previous
</Button>
<Button type="submit"
bsStyle="primary" className="next">
Next
</Button>
</div>
</Form>
)
}
export default reduxForm({
form: 'wizard', //Form name is same
destroyOnUnmount: false,
forceUnregisterOnUnmount: true, // <------ unregister fields on unmount
validate
})(WizardFormSecondPage)
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import validate from './validate'
import {
Form, FormGroup,
Button
} from 'react-bootstrap'
const colors = ['Red', 'Orange', 'Yellow', 'Green', 'Blue', 'Indigo', 'Violet']
const renderColorSelector = ({ input, meta: { touched, error } }) => (
<div>
<select {...input}>
<option value="">Select a color...</option>
{colors.map(val => (
<option value={val} key={val}>
{val}
</option>
))}
</select>
{touched && error && <span>{error}</span>}
</div>
)
const WizardFormThirdPage = props => {
const { handleSubmit, pristine, previousPage, submitting } = props
return (
<Form onSubmit={handleSubmit}>
<FormGroup>
<label>Favorite Color</label>
<Field name="favoriteColor" component={renderColorSelector} />
</FormGroup>
<FormGroup>
<label htmlFor="employed">Employed</label>
<div>
<Field
name="employed"
id="employed"
component="input"
type="checkbox"
/>
</div>
</FormGroup>
<FormGroup>
<label>Notes</label>
<div>
<Field name="notes" component="textarea" placeholder="Notes" />
</div>
</FormGroup>
<div>
<Button type="button" bsStyle="default"
className="previous" onClick={previousPage}>
Previous
</Button>
<Button type="submit" bsStyle="primary"
disabled={pristine || submitting}>
Submit
</Button>
</div>
</Form>
)
}
export default reduxForm({
form: 'wizard', //Form name is same
destroyOnUnmount: false,
forceUnregisterOnUnmount: true, // <------ unregister fields on unmount
validate
})(WizardFormThirdPage)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment