Reputation: 623
I want to create a Multi Step Form in React with the support of Microsoft UI Fabric.
Something like that:
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:
public render(): React.ReactElement<MyComponentProps> {
const list = [
{ headerText: 'Personal Details', component: <PersonalDetailsTab /> },
{ headerText: 'Company details', component: <CompanyDetailsTab /> },
{ headerText: 'Other', component: <OtherTab /> }
];
return (
<MultiStepForm steps={list} ></MultiStepForm>
);
}
This will be wonderful.
Notice that the three components inside doesn't know each other. Moreover:
... are totally independents
But of course I have problems when I want to manage state and validation between components. Mainly:
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.
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...
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).
Saddly I'm not a React expert. Possibly I don't want to use Redux or other framework.
This is my solution:
MyComponents.tsx:
export default class MyComponent extends React.Component<MyComponent> {
constructor(props: MyComponent) {
super(props);
this.state = {};
}
public render(): React.ReactElement<MyComponentProps> {
const list = [
{ headerText: 'Personal Details', component: <PersonalDetailsTab /> },
{ headerText: 'Company details', component: <CompanyDetailsTab /> },
{ headerText: 'Other', component: <OtherTab /> }
];
return (
<MultiStepForm steps={list} ></MultiStepForm>
);
}
}
MultiStepForm.tsx:
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<IMultiStepFormProps, IMultiStepFormState> {
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<IMultiStepFormProps> {
return (
<Pivot linkSize={PivotLinkSize.large} linkFormat={PivotLinkFormat.tabs}
selectedKey={`${this.state.selectedKey}`}
onLinkClick={this._onLinkClick} >
{this.state.steps.map((s, i) =>
<PivotItem headerText={s.headerText} itemKey={`${i}`} >
<StepTab
component={s.component} componentState={s.childrenState}
_handleBackClick={this._handleBackClick}
_handleNextClick={this._handleNextClick}
_updateComponent={this._updateComponent}
/>
</PivotItem>)}
</Pivot>
);
}
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
});
}
}
StepTab.tsx:
import * as React from 'react';
import { PrimaryButton } from 'office-ui-fabric-react';
export interface IStepProps {
component: React.Component;
componentState?: any;
_handleBackClick: () => void;
_handleNextClick: () => void;
_updateComponent: (comp: any, compState: any) => void;
}
export interface IStepState {
componentState: React.Component;
// _getComponentState: (x, y) => void;
}
export class StepTab extends React.Component<IStepProps, IStepState> {
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<IStepProps> {
return (
<form onSubmit={this._onSubmit.bind(this)} onChange={this.handleInputChange} >
{this.props.component}
<div style={{ textAlign: "right" }}>
<PrimaryButton text="BACK" onClick={(this._onBack.bind(this))} allowDisabledFocus={true} />
<PrimaryButton text="NEXT" type="submit"
allowDisabledFocus={true} />
</div>
</form>
);
}
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 });
}
}
ExamplesTabs.tsx:
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<{}, {}> {
public render(): React.ReactElement<{}> {
return (<>
<TextField name="name" label="Name"></TextField>
<TextField name="surname" label="Surname"></TextField>
{/* checkbox */}
</>);
}
}
export class CompanyDetailsTab extends React.Component<{}, {}> {
public render(): React.ReactElement<{}> {
return (<>
<TextField name="companyId" label="Company id" ></TextField>
<TextField name="companyName" label="Company Name"></TextField>
</>);
}
}
export class OtherTab extends React.Component<{}, {}> {
public render(): React.ReactElement<{}> {
return (<>
{/* bla bla */}
</>);
}
}
If someone have some suggestions or a valid alternative :)
Upvotes: 0
Views: 9267
Reputation: 86
here is good example by Brad https://github.com/bradtraversy/react_step_form, https://www.youtube.com/watch?v=zT62eVxShsY - guide
I have sandbox project with multi-step form with same approach, mb it's similar to what you want: https://github.com/AleksK1NG/React-meetuper/blob/master/client/src/components/MeetupCreate/MeetupCreateWizard/MeetupCreateWizard.js (MeetupCreate folder is multi-step form)
Hope you understand main idea of multi step form feature :)
Upvotes: 3