How to Write a Wizard in React
Building a sign-up or onboarding wizard shouldn’t be difficult if you have the right composition.
The wizard is a tried and true design interaction pattern. It’s great for on-boarding new users or inputting information tied to some type of decision tree.
Under the hood, a wizard nothing more than a state machine. A state machine is a simple object that keeps track of what state it’s in and the states it can transition to.
Wanna see some code? Here’s a github repo with these concepts applied and a live demo.
Setting up a Wizard
In React, a basic wizard looks like this:
It keep track the current step instate
and passes that down to the steps of the wizard. Our “state machine” is just a number. A set of Prev/Next buttons increment and decrement the currentStep
. (Later on, I’ll explain why that’s not such a good idea)
Each of the children components receives the current step of the wizard as props and can determine whether or not to display based on the value in that prop.
If the currentStep
is not correct, we bail early with a guard clause.
This is a basic pattern for a composition based wizard. It’s easy to modify — you can use CSS to show/hide steps instead of a guard clause, or use a renderStep
function to do a lookup on a decision tree to return the correct wizard step. The only limit is your imagination!
Let’s build on this foundation to deal with user input in each step.
Handing User Input in Each Step
In many cases, it’s a good idea to make the steps responsible for their own data, since it keeps things simple. If you don’t, you could end up with a very big wizard step wrapper component that handles the sanitizing, validation, and transformation of user input from all the steps in one place, and that will be hard to maintain.
Since each step is responsible for it’s own data, it should have some type of validation and handle any transformation of user input into an appropriate data structure. This also means that each form will have it’s own button that will move the currentStep
forward or backwards.
The basic wizard component outlined earlier will have to loose those Prev/Next buttons since the _next
and _prev
functions will be passed in as props.
And a step will look like this:
Note how this example is also missing a Prev button and _prev
. I did this to save time writing the examples. Many wizards, such as a sign-up wizard, don’t need a button that takes the user to the previous step. If this example did have that button, the _prev
function would mirror the _next
function.
Linkable Steps with location.hash
In some cases, it’s good to have the wizard steps be linkable (i.e. www.example.com/sign_up#step2). It’s pretty easy to handle this hash based navigation using vanilla JS:
State Machines are Cool
Tying this all together is the state machine that manages the current step.
If you’re working on a wizard that only has one possible set of screens — meaning that the choices the user makes don’t affect what screens are shown — the state machine outlined in the examples should work fine.
The path that a user follows to complete a wizard is made up of a series of wizard steps. When the required wizard steps change based on information the user has input (for example, signing up for a business account vs. a personal account), that’s when it’s necessary to use a more robust state machine to manage the user’s path. Having a place where the available steps are checked also makes it easier to do validations on past data and re-surface required or skipped steps.
State machines are a huge topic for another post. All I can say for now is that it’s very easy to use one in concert with the components and guidelines I’ve outlined above. The wizard is really the display mechanism for the state machine.
Don’t forget to check out the live demo and source code to see these concepts in action.