I am building a multi-step form (configurator) in React where some of the steps have child steps.
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:
- 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.
- 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.
- 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