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);
}<GoabxFormItem
requirement="required"
label="Name of item"
error={taskError ? "Please enter item name" : undefined}
helpText="Add an item to the dropdown list below">
<GoabxInput
onChange={(event: GoabInputOnChangeDetail) => setNewTask(event.value)}
name="item"
value={newTask}
/>
</GoabxFormItem>
<GoabxFormItem mt="l" label="Add to">
<GoabxRadioGroup
name="mountType"
onChange={(event: GoabRadioGroupOnChangeDetail) => onMountTypeChange(event.value)}
value={mountType}
orientation="horizontal">
<GoabxRadioItem value="prepend" label="Start" />
<GoabxRadioItem value="append" label="End" />
</GoabxRadioGroup>
</GoabxFormItem>
<GoabButtonGroup alignment="start" gap="relaxed" mt="l">
<GoabxButton type="primary" onClick={addTask}>
Add new item
</GoabxButton>
<GoabxButton type="tertiary" onClick={reset}>
Reset list
</GoabxButton>
</GoabButtonGroup>
<GoabDivider mt="l" />
<GoabxFormItem mt="l" label="All items">
<div style={{ width: isReset ? "320px" : "auto" }}>
<GoabxDropdown
key={tasks.length}
onChange={(event: GoabDropdownOnChangeDetail) =>
setSelectedTask(event.value as string)
}
value={selectedTask}
name="selectedTask">
{tasks.map(task => (
<GoabxDropdownItem
key={task.value}
value={task.value}
mountType={task.mount}
label={task.label}
/>
))}
</GoabxDropdown>
</div>
</GoabxFormItem>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;
}<goabx-form-item
requirement="required"
label="Name of item"
[error]="taskError ? 'Please enter item name' : undefined"
helpText="Add an item to the dropdown list below">
<goabx-input
name="item"
[value]="newTask"
(onChange)="onNewTaskChange($event)">
</goabx-input>
</goabx-form-item>
<goabx-form-item mt="l" label="Add to">
<goabx-radio-group
name="mountType"
[value]="mountType"
orientation="horizontal"
(onChange)="onMountTypeChange($event)">
<goabx-radio-item value="prepend" label="Start"></goabx-radio-item>
<goabx-radio-item value="append" label="End"></goabx-radio-item>
</goabx-radio-group>
</goabx-form-item>
<goab-button-group alignment="start" gap="relaxed" mt="l">
<goabx-button type="primary" (onClick)="addTask()">
Add new item
</goabx-button>
<goabx-button type="tertiary" (onClick)="reset()">
Reset list
</goabx-button>
</goab-button-group>
<goab-divider mt="l"></goab-divider>
<goabx-form-item mt="l" label="All items">
<ng-container *ngIf="renderTrigger">
<goabx-dropdown
[value]="selectedTask"
name="selectedTask"
(onChange)="onSelectedTaskChange($event)">
<goabx-dropdown-item
*ngFor="let task of tasks; trackBy: trackByFn"
[value]="task.value"
[mountType]="task.mount"
[label]="task.label">
</goabx-dropdown-item>
</goabx-dropdown>
</ng-container>
</goabx-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