Problem Space
I ran into a problem the other day working with the Fluent UI React component Controlled Multi-select Dropdown
. As context, here is my redacted code and explanation:
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react'; // office-ui-fabric-react@7.204.0
export interface State {
itemList: IDropdownOption[];
selectedItems: string[];
}
export default class DropDownExample extends React.Component<any, State> {
constructor(context) {
super(context);
this.state = {
itemList: [{ key: 'None', text: 'None' }],
selectedItems: []
}
this.GetItems();
this.LoadPreselection();
}
private async GetItems(){
// ... load items into itemList
}
private async LoadPreselection(){
// ... load pre-selected items into selectedItems
}
public onItemChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
if (item) {
let tempItemsArray = selectedKeys;
const index = tempItemsArray.indexOf(item.key, 0); // Obtain item index if exists - returns -1 if it does not
if (index > -1) {
tempItemsArray.splice(index, 1); // If the item exists, remove the item from the existing array through the splice function
} else {
tempItemsArray.push(item.key); // If the item does not exist, add the item to the existing array
}
setSelectedKeys(tempItemsArray); // Update State
}
}
render() {
return (
...
<Dropdown
placeholder="[Select Item]"
disabled={this.state.itemList.length === 0}
label='Items'
id='items'
responsiveMode={1}
multiSelect
options={this.state.itemList}
selectedKeys={this.state.selectedItems}
onChange={this.onItemChange.bind(this)}
/>
...
);
}
}
I had a list of items that I wanted to load into the Multi-select control from an API call. After obtaining the list of items, I wanted to preload any previously selected items.
At this point there were no problems…. until I had written my onchange
method. For some reason I was able to manage the list of selected keys absolutely fine, but there were no visible changes to the UI selection when clicking.
For example:
- Open drop down.
- Select an option.
- onChange event triggered (state managed).
- Selection in dropdown hasn’t changed (Item UI does not confirm selection OR un-selection)
As you can imagine, this caused some confusion. The SelectedKeys Array has the correct values but the control is not confirming what I am seeing behind the scenes.
This is until the penny dropped when reviewing example solutions.
Solution
The Dropdown control is treating the selectedKeys as an IMutable "(Once set, it cannot be changed)"
. This completely changes the onChanged method solution.
Rather than Push
to the existing
array, we need to use the Spread
syntax. The spread syntax will add the new item along with the already existing items into a new array
that can be used by the control. Such as:
...this.state.selectedItems, item.key as string
Rather than using Splice
to remove an element from an existing
array, we need to use the Filter
method. The filter method creates a new
array with all the elements that pass the logic test supplied, in this case bring everything over that doesn’t match the selected item. Such as:
this.state.selectedItems.filter(key => key !== item.key)
Combined, my onChange
method now takes on the following form and operates as expected:
public onItemChange = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
if (item) {
this.setState({
selectedItems:
item.selected
? [...this.state.selectedItems, item.key as string]
: this.state.selectedItems.filter(key => key !== item.key),
});
}
}