Skip to main content

Dynamic Dropdown Management

To handle dependencies between multiple dropdown fields dynamically in React, you need a flexible data structure that can represent parent-child relationships and dynamically update based on user selections. Here's a suggestion:

Data Structure

A nested tree-like structure is a good choice for representing hierarchical dependencies. Each level contains its own options and references its children.

const dependencies = {
country: {
options: ["USA", "Canada", "India"],
children: {
USA: {
state: {
options: ["California", "Texas"],
children: {
California: {
city: { options: ["Los Angeles", "San Francisco"] }
},
Texas: {
city: { options: ["Houston", "Dallas"] }
}
}
}
},
Canada: {
state: {
options: ["Ontario", "Quebec"],
children: {
Ontario: { city: { options: ["Toronto", "Ottawa"] } },
Quebec: { city: { options: ["Montreal", "Quebec City"] } }
}
}
}
}
},
productCategory: {
options: ["Electronics", "Furniture"],
children: {
Electronics: {
subCategory: {
options: ["Mobiles", "Laptops"],
children: {
Mobiles: { productName: { options: ["iPhone", "Samsung"] } },
Laptops: { productName: { options: ["MacBook", "Dell"] } }
}
}
},
Furniture: {
productName: { options: ["Sofa", "Table"] }
}
}
}
};

Implementation Steps

State Management: Use React's useState or a state management library (e.g., Redux, Zustand) to manage the current selections.

const [selections, setSelections] = useState({
country: "",
state: "",
city: "",
productCategory: "",
subCategory: "",
productName: ""
});

Dynamic Dropdowns: Render dropdowns based on the data structure and the current selection.

const getOptions = (field, currentSelection, dependencyData) => {
let options = dependencyData[field]?.options || [];
const keys = Object.keys(currentSelection).filter((key) => currentSelection[key]);
let pointer = dependencyData;

for (const key of keys) {
if (pointer[key] && pointer[key].children) {
pointer = pointer[key].children[currentSelection[key]];
}
}

return pointer?.[field]?.options || [];
};

Handle Change: Update the selections when a dropdown value changes and reset dependent fields.

const handleChange = (field, value) => {
setSelections((prev) => {
const newSelections = { ...prev, [field]: value };

// Reset dependent fields
const resetFields = Object.keys(prev).filter((key) => key > field);
resetFields.forEach((key) => {
newSelections[key] = "";
});

return newSelections;
});
};

Render Components: Render dropdowns dynamically based on the hierarchy.

return (
<div>
{Object.keys(dependencies).map((field) => (
<select
key={field}
value={selections[field]}
onChange={(e) => handleChange(field, e.target.value)}
>
<option value="">Select {field}</option>
{getOptions(field, selections, dependencies).map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</select>
))}
</div>
);

Explanation

dependencies:

Stores the hierarchical relationships. Supports dynamic children and options. selections:

Tracks the current user selection for each field. getOptions:

Dynamically fetches the options based on the current selection and the dependency tree. Dynamic Reset:

Resets dependent dropdown values when a parent value changes. This approach ensures scalability, maintains clarity, and is adaptable for various use cases like dynamic fields or varying depths.

Dependency Builder

1. Data Structure for Configuration

Assume the dropdown fields and their possible options are provided as:


const fieldsConfig = {
country: ["USA", "Canada", "India"],
state: ["California", "Texas", "Ontario", "Quebec"],
city: ["Los Angeles", "San Francisco", "Toronto", "Ottawa"],
productCategory: ["Electronics", "Furniture"],
subCategory: ["Mobiles", "Laptops"],
productName: ["iPhone", "Samsung", "MacBook", "Dell"],
};

2. Define the Dependency State

Create a state to store the dynamic dependency object:


type DependencyObject = Record<string, any>;

const [dependencies, setDependencies] = useState<DependencyObject>({});

3. UI Components

Here’s the React component to dynamically define dependencies:


import React, { useState } from "react";
import { Form, Select, Button, Card, Typography } from "antd";

const { Option } = Select;
const { Title } = Typography;

const fieldsConfig = {
country: ["USA", "Canada", "India"],
state: ["California", "Texas", "Ontario", "Quebec"],
city: ["Los Angeles", "San Francisco", "Toronto", "Ottawa"],
productCategory: ["Electronics", "Furniture"],
subCategory: ["Mobiles", "Laptops"],
productName: ["iPhone", "Samsung", "MacBook", "Dell"],
};

const DependencyBuilder: React.FC = () => {
const [dependencies, setDependencies] = useState<Record<string, any>>({});
const [currentField, setCurrentField] = useState<string>("");
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);

const handleAddDependency = () => {
if (!currentField || selectedOptions.length === 0) return;

setDependencies((prev) => ({
...prev,
[currentField]: {
options: selectedOptions,
children: {},
},
}));
setCurrentField("");
setSelectedOptions([]);
};

const renderDependencies = (obj: Record<string, any>, level = 0): JSX.Element[] =>
Object.keys(obj).map((key) => (
<Card
key={key}
title={key}
style={{ marginLeft: level * 20, marginBottom: 10 }}
bodyStyle={{ padding: 10 }}
>
<p>Options: {obj[key].options.join(", ")}</p>
{obj[key].children && renderDependencies(obj[key].children, level + 1)}
</Card>
));

return (
<div style={{ padding: 20 }}>
<Title level={3}>Dependency Builder</Title>
<Form layout="inline">
<Form.Item label="Field">
<Select
value={currentField}
onChange={(value) => setCurrentField(value)}
placeholder="Select Field"
style={{ width: 200 }}
>
{Object.keys(fieldsConfig).map((field) => (
<Option key={field} value={field}>
{field}
</Option>
))}
</Select>
</Form.Item>
<Form.Item label="Options">
<Select
mode="multiple"
value={selectedOptions}
onChange={(value) => setSelectedOptions(value)}
placeholder="Select Options"
style={{ width: 300 }}
>
{(fieldsConfig[currentField] || []).map((option) => (
<Option key={option} value={option}>
{option}
</Option>
))}
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" onClick={handleAddDependency}>
Add Dependency
</Button>
</Form.Item>
</Form>

<div style={{ marginTop: 20 }}>
<Title level={4}>Defined Dependencies</Title>
{renderDependencies(dependencies)}
</div>
</div>
);
};

export default DependencyBuilder;

4. Key Features

Field Selector:

Choose a field (e.g., country, state, city). Options are dynamically populated based on fieldsConfig. Dependency Management:

Add selected options and create child dependencies interactively. Visualization:

Dependencies are displayed hierarchically using nested Card components.

5. Extend with Children

Add functionality to nest dependencies:

Add a button next to each field in the rendered dependencies to "Add Child Dependency." Update the dependencies state by adding children to the selected parent node.