Tuesday, 23 August 2022

How to manage the display of a form with parent and child fields

I am building a multi-step form (configurator) in React where some of the steps have child steps.

Multi from UI

The steps (including dropdowns) are created based on the following JSON structure:

[
    {
      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",
        },
      ],
    },
  ];

Each dropdown in a step has options.

  • Some of the options have a connection to a child step.
  • But some child steps have options that are connected to other child steps.

I am struggling to achieve the combination of the following 3 requirements:

  1. When an option with a connection to a child step is selected, the child step appears and the same applies to the underlying child steps.
  2. When you change to another option in the top-level dropdown, not only the child of that option should collapse, but also the steps of underlying child steps.
  3. Only the form input of the selected and non-collapsed fields should be stored in (configuration) state (and later posted to a server).

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;

I am working on a solution for already 3 days and did not manage to find one. I have the feeling my approach is not the right one, so I hope someone can give me some advice or ideas on how to approach this problem in a smarter way.

My approaches:

  • Show (child) steps based on values in configuration state
  • Create a seperate state to store which fields are collapsed based on their id
  • Load the steps data in state and store step collapse and configuration under each step

Sandbox of my code:

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



from How to manage the display of a form with parent and child fields

No comments:

Post a Comment