Dynamically add an item to a dropdown list
type Task = {
value: string;
label: string;
mount: GoabDropdownItemMountType;
};
const DEFAULT_TASKS: Task[] = [
{ label: "Finish Report", value: "finish-report", mount: "append" },
{ label: "Attend Meeting", value: "attend-meeting", mount: "append" },
{ label: "Reply Emails", value: "reply-emails", mount: "append" },
];
const [tasks, setTasks] = useState<Task[]>(DEFAULT_TASKS);
const [newTask, setNewTask] = useState<string>("");
const [mountType, setMountType] = useState<string>("append");
const [selectedTask, setSelectedTask] = useState<string>("");
const [taskError, setTaskError] = useState<boolean>(false);
const [isReset, setIsReset] = useState<boolean>(false);
function onMountTypeChange(value: string | undefined) {
setMountType(value as string);
}
function addTask() {
if (newTask === "") {
setTaskError(true);
return;
}
setTaskError(false);
const task: Task = {
label: newTask,
value: newTask.toLowerCase().replace(" ", "-"),
mount: mountType as GoabDropdownItemMountType,
};
setTasks([...tasks, task]);
setNewTask("");
setIsReset(false);
}
function reset() {
setTasks(DEFAULT_TASKS);
setMountType("append");
setNewTask("");
setSelectedTask("");
setTaskError(false);
setIsReset(true);
}<GoabFormItem
requirement="required"
label="Name of item"
error={taskError ? "Please enter item name" : undefined}
helpText="Add an item to the dropdown list below"
>
<GoabInput
onChange={(event: GoabInputOnChangeDetail) => setNewTask(event.value)}
name="item"
value={newTask}
/>
</GoabFormItem>
<GoabFormItem mt="l" label="Add to">
<GoabRadioGroup
name="mountType"
onChange={(event: GoabRadioGroupOnChangeDetail) =>
onMountTypeChange(event.value)
}
value={mountType}
orientation="horizontal"
>
<GoabRadioItem value="prepend" label="Start" />
<GoabRadioItem value="append" label="End" />
</GoabRadioGroup>
</GoabFormItem>
<GoabButtonGroup alignment="start" gap="relaxed" mt="l">
<GoabButton type="primary" onClick={addTask}>
Add new item
</GoabButton>
<GoabButton type="tertiary" onClick={reset}>
Reset list
</GoabButton>
</GoabButtonGroup>
<GoabDivider mt="l" />
<GoabFormItem mt="l" label="All items">
<div style={{ width: isReset ? "320px" : "auto" }}>
<GoabDropdown
key={tasks.length}
onChange={(event: GoabDropdownOnChangeDetail) =>
setSelectedTask(event.value as string)
}
value={selectedTask}
name="selectedTask"
>
{tasks.map((task) => (
<GoabDropdownItem
key={task.value}
value={task.value}
mountType={task.mount}
label={task.label}
/>
))}
</GoabDropdown>
</div>
</GoabFormItem>defaultTasks: Task[] = [
{ label: "Finish Report", value: "finish-report", mount: "append" },
{ label: "Attend Meeting", value: "attend-meeting", mount: "append" },
{ label: "Reply Emails", value: "reply-emails", mount: "append" },
];
tasks: Task[] = [...this.defaultTasks];
newTask = "";
mountType: GoabDropdownItemMountType = "append";
selectedTask = "";
taskError = false;
renderTrigger = true;
onMountTypeChange(event: GoabRadioGroupOnChangeDetail): void {
this.mountType = event.value as GoabDropdownItemMountType;
}
onNewTaskChange(event: GoabInputOnChangeDetail): void {
this.newTask = event.value;
this.taskError = false;
}
onSelectedTaskChange(event: GoabDropdownOnChangeDetail): void {
this.selectedTask = event.value as string;
}
addTask(): void {
if (this.newTask === "") {
this.taskError = true;
return;
}
this.taskError = false;
const task: Task = {
label: this.newTask,
value: this.newTask.toLowerCase().replace(" ", "-"),
mount: this.mountType,
};
this.tasks =
this.mountType === "prepend" ? [task, ...this.tasks] : [...this.tasks, task];
this.newTask = "";
}
reset(): void {
this.newTask = "";
this.selectedTask = "";
this.taskError = false;
this.tasks = [...this.defaultTasks];
this.forceRerender();
}
forceRerender(): void {
this.renderTrigger = false;
setTimeout(() => {
this.renderTrigger = true;
}, 0);
}
trackByFn(index: number, item: Task): string {
return item.value;
}<goab-form-item
requirement="required"
label="Name of item"
[error]="taskError ? 'Please enter item name' : undefined"
helpText="Add an item to the dropdown list below"
>
<goab-input name="item" [value]="newTask" (onChange)="onNewTaskChange($event)">
</goab-input>
</goab-form-item>
<goab-form-item mt="l" label="Add to">
<goab-radio-group
name="mountType"
[value]="mountType"
orientation="horizontal"
(onChange)="onMountTypeChange($event)"
>
<goab-radio-item value="prepend" label="Start"></goab-radio-item>
<goab-radio-item value="append" label="End"></goab-radio-item>
</goab-radio-group>
</goab-form-item>
<goab-button-group alignment="start" gap="relaxed" mt="l">
<goab-button type="primary" (onClick)="addTask()"> Add new item </goab-button>
<goab-button type="tertiary" (onClick)="reset()"> Reset list </goab-button>
</goab-button-group>
<goab-divider mt="l"></goab-divider>
<goab-form-item mt="l" label="All items">
<ng-container *ngIf="renderTrigger">
<goab-dropdown
[value]="selectedTask"
name="selectedTask"
(onChange)="onSelectedTaskChange($event)"
>
@for (task of tasks; track task.value) {
<goab-dropdown-item
[value]="task.value"
[mountType]="task.mount"
[label]="task.label"
>
</goab-dropdown-item>
}
</goab-dropdown>
</ng-container>
</goab-form-item>const itemInput = document.getElementById("item-input");
const itemFormItem = document.getElementById("item-form-item");
const mountTypeGroup = document.getElementById("mount-type");
const addBtn = document.getElementById("add-btn");
const resetBtn = document.getElementById("reset-btn");
const dropdown = document.getElementById("task-dropdown");
let mountType = "append";
let newTask = "";
const defaultItems = [
{ value: "finish-report", label: "Finish Report" },
{ value: "attend-meeting", label: "Attend Meeting" },
{ value: "reply-emails", label: "Reply Emails" },
];
mountTypeGroup.addEventListener("_change", (e) => {
mountType = e.detail.value;
});
itemInput.addEventListener("_change", (e) => {
newTask = e.detail.value;
itemFormItem.removeAttribute("error");
});
addBtn.addEventListener("_click", () => {
if (newTask === "") {
itemFormItem.setAttribute("error", "Please enter item name");
return;
}
const newItem = document.createElement("goa-dropdown-item");
newItem.setAttribute("value", newTask.toLowerCase().replace(" ", "-"));
newItem.setAttribute("label", newTask);
newItem.setAttribute("mount", mountType);
dropdown.appendChild(newItem);
itemInput.value = "";
newTask = "";
});
resetBtn.addEventListener("_click", () => {
dropdown.innerHTML = "";
defaultItems.forEach((item) => {
const dropdownItem = document.createElement("goa-dropdown-item");
dropdownItem.setAttribute("value", item.value);
dropdownItem.setAttribute("label", item.label);
dropdown.appendChild(dropdownItem);
});
itemInput.value = "";
newTask = "";
itemFormItem.removeAttribute("error");
});<goa-form-item
version="2"
id="item-form-item"
requirement="required"
label="Name of item"
helptext="Add an item to the dropdown list below"
>
<goa-input version="2" id="item-input" name="item"></goa-input>
</goa-form-item>
<goa-form-item version="2" mt="l" label="Add to">
<goa-radio-group
version="2"
id="mount-type"
name="mountType"
value="append"
orientation="horizontal"
>
<goa-radio-item value="prepend" label="Start"></goa-radio-item>
<goa-radio-item value="append" label="End"></goa-radio-item>
</goa-radio-group>
</goa-form-item>
<goa-button-group alignment="start" gap="relaxed" mt="l">
<goa-button version="2" id="add-btn" type="primary">Add new item</goa-button>
<goa-button version="2" id="reset-btn" type="tertiary">Reset list</goa-button>
</goa-button-group>
<goa-divider mt="l"></goa-divider>
<goa-form-item version="2" mt="l" label="All items">
<goa-dropdown version="2" id="task-dropdown" name="selectedTask">
<goa-dropdown-item value="finish-report" label="Finish Report"></goa-dropdown-item>
<goa-dropdown-item value="attend-meeting" label="Attend Meeting"></goa-dropdown-item>
<goa-dropdown-item value="reply-emails" label="Reply Emails"></goa-dropdown-item>
</goa-dropdown>
</goa-form-item>Allow users to add new items to a dropdown list dynamically.
When to use
Use this pattern when:
- Users need to add custom options to a predefined list
- The list of options can grow based on user input
- You want to provide flexibility while maintaining structure
Considerations
- Use the
mountTypeprop to control where new items appear (prepend or append) - Validate input before adding to prevent empty or duplicate entries
- Provide a reset option to restore the original list
- Show clear feedback when items are added successfully