Collecting data from a wizard form

27 Dec 2016

You’ve all seen long forms… they are a nightmare even to look at! You probably have at least one in your app too. A checkout page, maybe.

Just today your designer said:

What if we split this checkout form into several “steps”? The users will feel a lot better about filling it!

You want your users to be awesome! So you took a stab and made this form multi-step. Every page is a form container of its own, and then there’s a “main” container which does the page switching and form submitting… Something like this, perhaps:

class CheckoutForm extends React.Component {
  constructor() {
    super();
    this.state = {
      step: 1,
    };
    this.goToNext = this.goToNext.bind(this);
  }

  goToNext() {
    const { step } = this.state;
    if (step !== 3) {
      this.setState({ step: step + 1 });
    } else {
      alert('Submitting');
      // BUT
      // how to access all the fields from here?
    }
  };

  render() {
    switch (this.state.step) {
      case 1:
        return <Personal onSubmit={this.goToNext} />;
      case 2:
        return <Shipping onSubmit={this.goToNext} />;
      case 3:
        return <Billing onSubmit={this.goToNext} />;
    }
  }
}

class Personal extends React.Component {
  constructor() {
    super();
    this.state = {
      name: '',
    };
  }
}
// and so on for Shipping and Billing

Here’s a JS Bin with a demo:

JS Bin on jsbin.com (The example is intentionally simplified to keep the sources brief. The real checkout form will likely be more complex: state and country selectors, credit card inputs, etc.)

Eh, cool, you think. But now… when the last step is submitted, how do you collect the input values from the previous steps?

Sending the data from the last step only is clearly not an option! The server will carelessly put a REJECTED 🚫 stamp onto your request if it gets only the billing info without the personal or shipping. It needs to know where to ship, after all…

Right now, each step “owns” its data — it’s simply not available outside of it. But we need it to be.

There are several directions we could take:

If it sounds anything like uncontrolled vs. controlled components… that’s because it is.

Currently, each step is “uncontrolled” from the perspective of CheckoutForm, which may seem simpler to implement, but it ends up being harder to maintain, read, and use — not so fit for any wizard form.

Instead, what we could do is make every step component receive the values for the fields as well as onChange handlers for each of these from the main form component. A step is just a bundle of controlled inputs, after all.

class CheckoutForm extends React.Component {
  constructor() {
    super();
    this.state = {
      name: '',
      shipping_line: '',
      billing_line: '',
    };
    // other inputs omitted for brevity
    // see full code on JS Bins
  }

  // goToNext and handleChange

  render() {
    switch (this.state.step) {
      case 1:
        return <Personal
          name={this.state.name}
          onChangeName={this.handleChange('name')}
          onSubmit={this.goToNext}
        />;
      case 2:
        return <Shipping
          line={this.state.shipping_line}
          onChangeLine={this.handleChange('shipping_line')}
          onSubmit={this.goToNext}
        />;
      case 3:
        return <Billing
          line={this.state.billing_line}
          onChangeLine={this.handleChange('billing_line')}
          onSubmit={this.goToNext}
        />;
    }
  }
}

Then accessing all the field values will be pretty easy when handing onSubmit for Billing It’s all living in the state of CheckoutForm already and we can just use all the values we need to make a proper request:

class CheckoutForm extends React.Component {
  goToNext() {
    const { step } = this.state;
    if (step !== 3) {
      this.setState({ step: step + 1 });
    } else {
      const values = {
        name: this.state.name,
        shipping_line: this.state.shipping_line,
        billing_line: this.state.billing_line,
      };
      // submit to API somehow
    }
  };
}

Here’s the final JS bin:

JS Bin on jsbin.com

Sassy!

If you want to learn more about handling forms with React, be sure to sign up for my newsletter.

You know how you tear your hair out when your designer asks to make a nice form?

Because forms seem such an outlier in React... And yet, they used to be simpler back in the day.

But what if you could implement the form experience your users deserve? Imagine if making a form component were as simple as rendering a link!

My upcoming book can help you get there. You will learn how forms fit into the React model; you will learn why you should model your data first (and how do do it). And, you are going to see how to implement common form patterns and structures.

No spam, promise. I hate it as much as you do!

, enjoying the article? Now think of 3 friends who are interested in React, Forms and would be into it, and share the link with them! 👇

https://goshakkk.name/wizard-form-collect-info/