javascript - 如何管理带有父字段和子字段的表单的显示

标签 javascript reactjs

我正在 React 中构建一个多步骤表单(配置器),其中一些步骤具有子步骤。

Multi from UI

步骤(包括下拉菜单)是基于以下 JSON 结构创建的:

[
    {
      id: "step_a",
      title: "Step A",
      options: [
        {
          id: "1",
          title: "Option 1",
          child_steps: ["step_b"],
        },
        {
          id: "2",
          title: "Option 2",
          child_steps: ["step_c"],
        },
      ],
    },
    {
      id: "step_b",
      title: "Step B",
      parent_step: "step_a",
      options: [
        {
          id: "1",
          title: "Option 1",
        },
        {
          id: "2",
          title: "Option 2",
        },
      ],
    },
    {
      id: "step_c",
      title: "Step C",
      parent_step: "step_a",
      options: [
        {
          id: "1",
          title: "Option 1",
          child_steps: ["step_d"],
        },
        {
          id: "2",
          title: "Option 2",
        },
      ],
    },
    {
      id: "step_d",
      title: "Step D",
      parent_step: "step_c",
      options: [
        {
          id: "1",
          title: "Option 1",
        },
        {
          id: "2",
          title: "Option 2",
        },
      ],
    },
  ];

步骤中的每个下拉菜单都有选项。

  • 一些选项与子步骤有关。
  • 但是一些子步骤具有与其他子步骤相关的选项。

我正在努力实现以下 3 个要求的组合:

  1. 选择与子步骤连接的选项时,出现子步骤,并且同样适用于基础子步骤。
  2. 当您更改为顶级下拉列表中的另一个选项时,不仅该选项的子级应该折叠,而且底层子步骤的步骤也应该折叠。
  3. 只有选定字段和非折叠字段的表单输入应以(配置)状态存储(并稍后发布到服务器)。

App.js

import "./styles.css";
import steps from "./steps.js";
import Step from "./components/Step";

export default function App() {
  return (
    <div className="App">
      <div className="steps">
        {steps.map((step) => (
          <Step key={step.id} step={step} />
        ))}
      </div>
    </div>
  );
}

Step.js

import { ConfiguratorContext } from "../context/ConfiguratorContext"; // Holds configuration state only

const Step = ({ step }) => {
  const { id, title, options, parent_step } = step;
  const [configuration, setConfiguration] = useContext(ConfiguratorContext);

  const handleChange = (e) => {
    const value = e.target.value;
    // Store value of current step to later update configuration in state
    const configurationUpdate = { [id]: value };
    if (options && options.length > 0) {
      options.forEach((option) => {
        // Get current selected option and check whether it has child steps
        if (option.id === value && option.child_steps) {
          option.child_steps.forEach((stepId) => {
            // Prepare values of child steps so they can be stored in configuration state
            const childStep = steps.find((step) => step.id === stepId);
            if (childStep) {
              configurationUpdate[stepId] = childStep.id;
            }
          });
        }
      });
    }
    setConfiguration((prevConfig) => ({
      ...prevConfig,
      ...configurationUpdate
    }));
  };

  const getClassName = () => {
    const classes = ["step"];
    if (parent_step && !configuration[parent_step] && !configuration[id])
      classes.push("d-none");
    return classes.join(" ");
  };

  return (
    <div className={getClassName()}>
      <label>{title}</label>
      <select className="step-dropdown" onChange={handleChange}>
        <option value="">-- Select --</option>
        {options.map((option) => (
          <option key={option.id} value={option.id}>
            {option.title}
          </option>
        ))}
      </select>
    </div>
  );
};

export default Step;

我已经研究了 3 天的解决方案,但未能找到解决方案。我觉得我的方法不对,所以我希望有人能给我一些建议或想法,告诉我如何以更聪明的方式解决这个问题。

我的方法:

  • 根据配置状态中的值显示(子)步骤
  • 创建一个单独的状态来存储根据其 id 折叠的字段
  • 加载state中的steps数据,存储每一步下的step collapse和configuration

我的代码沙盒:

https://codesandbox.io/embed/epic-almeida-04ykwe?fontsize=14&hidenavigation=1&theme=dark

最佳答案

您可以使用以下内容,

App.js

import steps from "./steps.js";
import Step from "./components/Step";

export default function App() {
  return (
    <div className="App">
      <div className="steps">
       {/* iterate through all root level steps (ignore steps with parent step) */}
        {steps.filter(x => !x.parent_step).map((step) => (
          <Step key={step.id} step={step} />
        ))}
      </div>
    </div>
  );
}

Step.js

import { useMemo, useState } from "react";
import steps from "../steps";

const Step = ({ step }) => {
  const { id, title, options, parent_step } = step;

  const handleChange = (e) => {
    const value = e.target.value;
    setSelectedStep(value);
  };

  const getClassName = () => {
    const classes = ["step"];
    return classes.join(" ");
  };
  {/* state to save selected option */}
  const [selectedStep, setSelectedStep] = useState("");
  {/* Calculate the child steps from the selected step (from current steps options get the steps specified by child steps) */}
  const childSteps = useMemo(() => step.options.find(y => y.id === selectedStep)?.child_steps?.map(x => steps.find(z => z.id === x)), [selectedStep, step.options]);

  return (
    <div className={getClassName()}>
      <label>{title}</label>
      <select className="step-dropdown" onChange={handleChange}>
        <option value="">-- Select --</option>
        {options.map((option) => (
          <option key={option.id} value={option.id}>
            {option.title}
          </option>
        ))}
      </select>
      {/* Using recursion to iterate through child steps */}
      {childSteps && childSteps.map((s, i) => <Step key={i} step={s} />)}
    </div>
  );
};

export default Step;

关于javascript - 如何管理带有父字段和子字段的表单的显示,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73402886/

相关文章:

javascript - div样式随动画变化?

javascript - 如何延迟 jQuery 中值的更改?

javascript - 带有异步调用的同步循环

javascript - bool 和文本字段验证 - Javascript

Reactjs:为什么使用 const {} = this.props 以及为什么将其放在渲染函数中

javascript - 在 React 中填充初始状态

javascript - 条件渲染不能防止渲染

javascript - 在动态添加的元素上添加目标

javascript - 带有 Typescript 的 ReactJS - 找不到变量 : require

javascript - Pubnub 多次发送消息