title: Multi Step form in React
body: <p>I want to create a Multi Step Form in React with the support of Microsoft UI Fabric.</p>
  
  <p>Something like that:</p>
  
  <p><a href="https://i.stack.imgur.com/buDw6.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/buDw6.png" alt="Multistep Form mockup"></a></p>
  
  <p>So, I want to create a dynamic multi step form where the inside components are totally independent of each other (unless there are some explicit dependencies).
  I want to accomplish something like that:</p>
  
  <pre><code>public render(): React.ReactElement&lt;MyComponentProps&gt; {
  
      const list = [
        { headerText: 'Personal Details', component: &lt;PersonalDetailsTab /&gt; },
        { headerText: 'Company details', component: &lt;CompanyDetailsTab /&gt; },
        { headerText: 'Other', component: &lt;OtherTab /&gt; }
      ];
  
      return (
        &lt;MultiStepForm steps={list} &gt;&lt;/MultiStepForm&gt;
      );
  }
  </code></pre>
  
  <p>This will be wonderful.</p>
  
  <p>Notice that the three components inside doesn't know each other.
  Moreover:</p>
  
  <ul>
  <li>State management</li>
  <li>UI rappresentation</li>
  <li>Validation</li>
  </ul>
  
  <p>... are totally independents</p>
  
  <p>But of course I have problems when I want to manage state and validation between components.
  Mainly:</p>
  
  <ul>
  <li><p>when I click on the NEXT button from a Tab and go to the next tab, and then I want to go back to the previous tab again by clicking on the PREV button, I have lost the data of the fields filled previously.</p></li>
  <li><p>the next Tab must also be able to depend on the data entered in a previous tab (for example in the third Tab a service is called passing the surname and the company ID). I imagine that in this way independence and decoupling will be lost...</p></li>
  <li><p>when I click on NEXT from a Tab and go to the next tab, I believe there should be a mechanism where the state must respects some constraints (stupid example: the second Tab does not accept any person who was previously named with a "Rossi" surname... The second tab (or the first?) should show a validation error).</p></li>
  </ul>
  
  <p>Saddly I'm not a React expert. Possibly I don't want to use Redux or other framework.</p>
  
  <p>This is my solution:</p>
  
  <p><strong>MyComponents.tsx</strong>:</p>
  
  <pre><code>export default class MyComponent extends React.Component&lt;MyComponent&gt; {
  
      constructor(props: MyComponent) {
      super(props);
  
      this.state = {};
      }
  
      public render(): React.ReactElement&lt;MyComponentProps&gt; {
          const list = [
            { headerText: 'Personal Details', component: &lt;PersonalDetailsTab /&gt; },
            { headerText: 'Company details', component: &lt;CompanyDetailsTab /&gt; },
            { headerText: 'Other', component: &lt;OtherTab /&gt; }
          ];
  
          return (
              &lt;MultiStepForm steps={list} &gt;&lt;/MultiStepForm&gt;
          );
      }
  }
  </code></pre>
  
  <p><strong>MultiStepForm.tsx</strong>:</p>
  
  <pre><code>import * as React from 'react';
  import { Pivot, PivotItem, PivotLinkSize, PivotLinkFormat } from 'office-ui-fabric-react';
  import { StepTab } from '../StepTab';
  
  interface IMultiStepFormProps {
      steps: any[];
  }
  
  interface IMultiStepFormState {
      steps: any;
      stepsCount: number;
      selectedKey: number;
  }
  
  export class MultiStepForm extends React.Component&lt;IMultiStepFormProps, IMultiStepFormState&gt; {
  
      constructor(props: IMultiStepFormProps) {
          super(props);
  
          this._handleBackClick = this._handleBackClick.bind(this);
          this._handleNextClick = this._handleNextClick.bind(this);
          this._onLinkClick = this._onLinkClick.bind(this);
          this._updateComponent = this._updateComponent.bind(this);
  
          this.state = {
              selectedKey: 0,
              steps: props.steps,
              stepsCount: props.steps.length
          };
      }
  
      public render(): React.ReactElement&lt;IMultiStepFormProps&gt; {
          return (
              &lt;Pivot linkSize={PivotLinkSize.large} linkFormat={PivotLinkFormat.tabs}
                  selectedKey={`${this.state.selectedKey}`}
                  onLinkClick={this._onLinkClick} &gt;
                  {this.state.steps.map((s, i) =&gt;
                      &lt;PivotItem headerText={s.headerText} itemKey={`${i}`} &gt;
                          &lt;StepTab
                              component={s.component} componentState={s.childrenState}
                              _handleBackClick={this._handleBackClick}
                              _handleNextClick={this._handleNextClick}
                              _updateComponent={this._updateComponent}
                          /&gt;
                      &lt;/PivotItem&gt;)}
              &lt;/Pivot&gt;
          );
      }
  
  
      private _updateComponent(comp, compState) {
          // work in progress...
          var s = this.state.steps;
          s[this.state.selectedKey].component = comp;
          s[this.state.selectedKey].childrenState = compState;
          this.setState({
              steps: s
          });
      }
  
      private _handleBackClick(): void {
          let currentIndex = this.state.selectedKey;
          let count = this.state.stepsCount;
          let newIndex = ((currentIndex - 1) + count) % count;
          this.setState({
              selectedKey: newIndex
          });
      }
      private _handleNextClick(): void {
          let currentIndex = this.state.selectedKey;
          let count = this.state.stepsCount;
          let newIndex = (currentIndex + 1) % count;
          this.setState({
              selectedKey: newIndex
          });
      }
      private _onLinkClick(item: PivotItem): void {
          let newIndex = parseInt(item.props.itemKey);
          this.setState({
              selectedKey: newIndex
          });
      }
  
  }
  </code></pre>
  
  <p><strong>StepTab.tsx</strong>:</p>
  
  <pre><code>import * as React from 'react';
  import { PrimaryButton } from 'office-ui-fabric-react';
  
  export interface IStepProps {
      component: React.Component;
      componentState?: any;
      _handleBackClick: () =&gt; void;
      _handleNextClick: () =&gt; void;
      _updateComponent: (comp: any, compState: any) =&gt; void;
  }
  export interface IStepState {
      componentState: React.Component;
      // _getComponentState: (x, y) =&gt; void;
  }
  export class StepTab extends React.Component&lt;IStepProps, IStepState&gt; {
      constructor(props) {
          super(props);
  
          this.handleInputChange = this.handleInputChange.bind(this);
  
          this.state = {
              componentState: this.props.componentState || {},
              // _getComponentState: this.props._updateComponent
          };
      }
  
      public componentDidMount() {
          console.log("StepTab MOUNT");
          if (this.state.componentState) {
              // work in progress
  
          }
      }
  
      public render(): React.ReactElement&lt;IStepProps&gt; {
          return (
              &lt;form onSubmit={this._onSubmit.bind(this)} onChange={this.handleInputChange} &gt;
                  {this.props.component}
                  &lt;div style={{ textAlign: "right" }}&gt;
                      &lt;PrimaryButton text="BACK" onClick={(this._onBack.bind(this))} allowDisabledFocus={true} /&gt;
                      &lt;PrimaryButton text="NEXT" type="submit"
                          allowDisabledFocus={true} /&gt;
                  &lt;/div&gt;
              &lt;/form&gt;
          );
      }
      public _onBack(e) {
          e.preventDefault();
          this.props._handleBackClick();
      }
      public _onSubmit(e) {
          e.preventDefault();
          this.props._updateComponent(this.props.component, this.state.componentState);
          this.props._handleNextClick();
      }
      public handleInputChange(event) {
          const target = event.target;
          const value = target.type === 'checkbox' ? target.checked : target.value;
          const name = target.name || target.id;
          var partialState = this.state.componentState;
          partialState[name] = value;
          this.setState({ componentState: partialState });
      }
  }
  </code></pre>
  
  <p><strong>ExamplesTabs.tsx</strong>:</p>
  
  <pre><code>import * as React from 'react';
  import {
      TextField, ITextFieldProps, Stack, IconButton, ComboBox, Label, MaskedTextField,
      Rating, IComboBoxOption, SelectableOptionMenuItemType, getId
  } from 'office-ui-fabric-react';
  
  export class PersonalDetailsTab extends React.Component&lt;{}, {}&gt; {
      public render(): React.ReactElement&lt;{}&gt; {
          return (&lt;&gt;
              &lt;TextField name="name" label="Name"&gt;&lt;/TextField&gt;
              &lt;TextField name="surname" label="Surname"&gt;&lt;/TextField&gt;
              {/* checkbox */}
          &lt;/&gt;);
      }
  }
  
  export class CompanyDetailsTab extends React.Component&lt;{}, {}&gt; {
      public render(): React.ReactElement&lt;{}&gt; {
          return (&lt;&gt;
              &lt;TextField name="companyId" label="Company id" &gt;&lt;/TextField&gt;
              &lt;TextField name="companyName" label="Company Name"&gt;&lt;/TextField&gt;
          &lt;/&gt;);
      }
  }
  
  export class OtherTab extends React.Component&lt;{}, {}&gt; {
      public render(): React.ReactElement&lt;{}&gt; {
          return (&lt;&gt;
              {/* bla bla */}
          &lt;/&gt;);
      }
  }
  </code></pre>
  
  <p>If someone have some suggestions or a valid alternative :) </p>