我想在Microsoft UI Fabric的支持下在React中创建一个多步骤表单。

像这样:

javascript - React中的多步骤表单-LMLPHP

因此,我想创建一个动态的多步骤表单,其中内部组件完全彼此独立(除非存在一些明确的依赖关系)。
我想完成这样的事情:

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>
    );
}


这将是美好的。

请注意,其中的三个组件彼此不认识。
此外:


国家管理
UI再现
验证方式


...是完全独立的

但是,当我要管理组件之间的状态和验证时,我当然会遇到问题。
主要是:


当我单击选项卡上的“下一个”按钮并转到下一个选项卡,然后又想通过单击“上一步”按钮返回到上一个选项卡时,我丢失了先前填充的字段的数据。
下一个选项卡也必须能够依赖在上一个选项卡中输入的数据(例如,在第三个选项卡中,称为传递姓氏和公司ID的服务)。我想这样就不会失去独立性和脱钩性。
当我从选项卡单击NEXT并转到下一个选项卡时,我认为应该有一种机制,该状态必须遵守某些约束条件(愚蠢的示例:第二个选项卡不接受以前用“ Rossi”命名的任何人)姓...第二个标签(或第一个标签?)应显示验证错误)。


遗憾的是我不是React专家。可能我不想使用Redux或其他框架。

这是我的解决方案:

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 });
    }
}


示例Tabs.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 */}
        </>);
    }
}


如果有人提出建议或有效的替代方法:)

最佳答案

这是Brad https://github.com/bradtraversy/react_step_form的好例子,
https://www.youtube.com/watch?v=zT62eVxShsY-指南

我有一个沙箱项目,采用了多步表单,使用了相同的方法,但它与您想要的类似:
https://github.com/AleksK1NG/React-meetuper/blob/master/client/src/components/MeetupCreate/MeetupCreateWizard/MeetupCreateWizard.js(MeetupCreate文件夹是多步骤表单)

希望您了解多步骤表单功能的主要思想:)

07-26 01:19