How to add tags in a Fluent Ui picker

Sebastian Serban
3 min readNov 24, 2020

--

Recently we started using Fluent Ui in our react applications. This library has many useful controls and the documentation is great. But I found a bit of a challenge to use the tag picker in a form and create categories on the fly, from this control by adding an Add button.

I checked the docs and this looked pretty straight forward, but little did I know that there are multiple props to get this behavior. Here is my implementation.

Wont go over the details of creating a new react app (if you are reading this, you already done that)

Start by adding the tag picker into your app:

 const inputProps: IInputProps = {
placeholder: 'Search for Categories'
};
const pickerSuggestionsProps: IBasePickerSuggestionsProps = {
suggestionsHeaderText: 'Existing categories!',
noResultsFoundText: 'No results found!',
},
};

onResolveSuggestions = async (
filter: string,
selectedItems: ITag[] | undefined
): Promise<ITag[]> => {
if (filter) {
return form.categories
.filter((category: Category) => category.text.toLowerCase().includes(filter.toLowerCase()))
.map((category: Category): ITag => {
return {
name: category.text,
key: category.id
};
});
}
return [];
};
onChange = (items?: ITag[] | undefined) => {
/* notify category change */
};

const getTextFromItem = (item: ITag) => item.name;
return (
<TagPicker
onResolveSuggestions={onResolveSuggestions}
getTextFromItem={getTextFromItem}
pickerSuggestionsProps={pickerSuggestionsProps}
inputProps={inputProps}
onChange={onChange}
/>
)

So we have some categories received in props, a callback to filter that categories when we write something into the tag picker and a change handler to update the selected category id.

Ok, but when we type a category that does not exist… we have to add it. How? by calling the server’s endpoint. And would be nice if we had a button rendered when no suggestions are available.

Turns out the we have a property on IBasePickerSuggestionsProps called onRenderNoResultFound.

/**
* How the "no result found" should look in the suggestion list.
*/
onRenderNoResultFound?: IRenderFunction<void>;

Cool, not let’s add this on our pickerSuggestionsProps.

onRenderNoResultFound: () => {
return (
<CommandBarButton iconProps={addIcon} styles={buttonStyles} text="Add category" onClick={() => {
tagPickerRef.current?.completeSuggestion(true);
}} />
);
}

but we actually need to drag in a tag ref. Why? Because we will have to notify the tag picker control that we finished typing and want to complete the suggestions.

When suggestions are completed, the picker component will search for the callbacks:

/**
* A function used to validate if raw text entered into the well can be added into the selected items list
*/
onValidateInput?: (input: string) => ValidationState;
/**
* Function that specifies how arbitrary text entered into the well is handled.
*/
createGenericItem?: (input: string, ValidationState: ValidationState) => ISuggestionModel<T> | T;
/**
* A callback to process a selection after the user selects something from the picker. If the callback returns null,
* the item will not be added to the picker.
*/

onItemSelected?: (selectedItem?: T) => T | PromiseLike<T> | null;

I think this is self explanatory but let me add just a few words.

onValidateInput will indicate weather the input text is eligible to create a tag. I my case was just a truthy condition, basically I did not want to create empty tags.

createGenericItem will transform our input string into an ITag object.

onItemSelected this is where the magic happens. When this is implemented we can check if the key does not exist and return a Promise of ITag. I just returned the promise that adds the category on the server.

<TagPicker
onResolveSuggestions={onResolveSuggestions}
getTextFromItem={getTextFromItem}
pickerSuggestionsProps={pickerSuggestionsProps}
inputProps={inputProps}
onChange={onChange}
onValidateInput={(input: string) => {
return input ? ValidationState.valid : ValidationState.invalid;
}}
createGenericItem={(input: string) => {
return { key: '', name: input } as ITag;
}}
onItemSelected={(item?: ITag | undefined) => {
if (!item?.key) {
return onAddNewCategory(item?.name || '');
}
return;
}}
/>

There you go!

I may have a couple of typo’s in this code, but if you are looking for a quick implementation, check out this link https://github.com/ssebastianp/fluent-tag-picker-add-tags/blob/master/src/components/CategoryPicker.tsx

--

--