-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
/
MultistepWizard.js
148 lines (138 loc) · 4.15 KB
/
MultistepWizard.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import React, { useState } from 'react';
import { ErrorMessage, Field, Form, Formik } from 'formik';
import * as Yup from 'yup';
import { Debug } from './Debug';
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// Wizard is a single Formik instance whose children are each page of the
// multi-step form. The form is submitted on each forward transition (can only
// progress with valid input), whereas a backwards step is allowed with
// incomplete data. A snapshot of form state is used as initialValues after each
// transition. Each page has an optional submit handler, and the top-level
// submit is called when the final page is submitted.
const Wizard = ({ children, initialValues, onSubmit }) => {
const [stepNumber, setStepNumber] = useState(0);
const steps = React.Children.toArray(children);
const [snapshot, setSnapshot] = useState(initialValues);
const step = steps[stepNumber];
const totalSteps = steps.length;
const isLastStep = stepNumber === totalSteps - 1;
const next = values => {
setSnapshot(values);
setStepNumber(Math.min(stepNumber + 1, totalSteps - 1));
};
const previous = values => {
setSnapshot(values);
setStepNumber(Math.max(stepNumber - 1, 0));
};
const handleSubmit = async (values, bag) => {
if (step.props.onSubmit) {
await step.props.onSubmit(values, bag);
}
if (isLastStep) {
return onSubmit(values, bag);
} else {
bag.setTouched({});
next(values);
}
};
return (
<Formik
initialValues={snapshot}
onSubmit={handleSubmit}
validationSchema={step.props.validationSchema}
>
{formik => (
<Form>
<p>
Step {stepNumber + 1} of {totalSteps}
</p>
{step}
<div style={{ display: 'flex' }}>
{stepNumber > 0 && (
<button onClick={() => previous(formik.values)} type="button">
Back
</button>
)}
<div>
<button disabled={formik.isSubmitting} type="submit">
{isLastStep ? 'Submit' : 'Next'}
</button>
</div>
</div>
<Debug />
</Form>
)}
</Formik>
);
};
const WizardStep = ({ children }) => children;
const App = () => (
<div>
<h1>Formik Multistep Wizard</h1>
<Wizard
initialValues={{
email: '',
firstName: '',
lastName: '',
}}
onSubmit={async values =>
sleep(300).then(() => console.log('Wizard submit', values))
}
>
<WizardStep
onSubmit={() => console.log('Step1 onSubmit')}
validationSchema={Yup.object({
firstName: Yup.string().required('required'),
lastName: Yup.string().required('required'),
})}
>
<div>
<label htmlFor="firstName">First Name</label>
<Field
autoComplete="given-name"
component="input"
id="firstName"
name="firstName"
placeholder="First Name"
type="text"
/>
<ErrorMessage className="error" component="div" name="firstName" />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<Field
autoComplete="family-name"
component="input"
id="lastName"
name="lastName"
placeholder="Last Name"
type="text"
/>
<ErrorMessage className="error" component="div" name="lastName" />
</div>
</WizardStep>
<WizardStep
onSubmit={() => console.log('Step2 onSubmit')}
validationSchema={Yup.object({
email: Yup.string()
.email('Invalid email address')
.required('required'),
})}
>
<div>
<label htmlFor="email">Email</label>
<Field
autoComplete="email"
component="input"
id="email"
name="email"
placeholder="Email"
type="text"
/>
<ErrorMessage className="error" component="div" name="email" />
</div>
</WizardStep>
</Wizard>
</div>
);
export default App;