Searchable tool bar with Semantic UI React with user input

Will Ley
Will Ley
Sep 6, 2018 · 5 min read

I should start by saying I have a bit of a love hate relationship with Semantic UI and it’s react components. It’s mostly when I go to style and likely due to my lack of knowledge around flex box and the way the components interact with each other. With that said, I love the ‘drag’ and drop way the library is set up and truly appreciate the work that went into the design of the library for the community.

On a recent project I was building a note application and I needed a way for users to add categories to their note. They needed both a way to see and use existing categories and also add their own that could the be persisted for everyone to use. For this project I used a Rails back end with a React front-end using Redux and Thunk. I found it incredibly useful to have the multi-select and searchable drop down element from Semantic UI React. You can find the core code from Semantic here and look specifically for the multiple search selection component. With two added props, it has the capability to have the user add an item to the menu with the wonderfully semantic prop functions/methods allowAdditions and onAddItem .

The first, allowAdditions , is a simple flag for the component to allow for addition of items. If the user types in the menu it will cycle up the items that are already in the list. If the item isn’t found the user simply clicks ‘return’ and the item is added. This is where the onAddItem function comes into play and where our struggles, I mean magic, and the reason for writing this, begins.

We’re going to build this in React to start with local state and in a future post I’ll show you how you can move this to store for ease and use in other parts of an app.

The core features:

The drop down menu gets it’s options from a prop called, options. This is semantic at it’s best and part of why I enjoy using it is the human understandable syntax of props in its component construction. Two things to remember about option is that it is an array and must be so at all times. The next thing is that the formatting for it’s elements are:

{key: 1 value: 1 text: "One is loneliest number..."}

So the options array looks something like this:

optionsArray = [
{key: 1 value: 1 text: "A partridge in a pear tree"},
{key: 2 value: 2 text: "Two turtle doves"},
{key: 3 value: 3 text: "Three french hens"}
]

Now for the component:

<Dropdown
placeholder='Type to search categories or to add a new one'
fluid
multiple
search
selection
allowAdditions //<-----this is the flag for additions
onAddItem={this.handleOnAdd}//<---this is our handler options= {optionsArray} //<------this is where the options govalue={this.state.dropDownValueArray} //<--this is where it keeps
//its values of what is selected

onChange={(e,optionsObj) => this.updateCategories(optionsObj.value)
//I know this looks odd, we'll get to this later
/>

Three of these pieces, allowAdditions onAddItem options we’ve now talked about. Using React, I would move the optionsArray to state. This allows for a fetch call to your database to retrieve your current options and update the element on componentDidMount(). This goes with dropDownValueArray which holds all of the selected values. So our state looks like:

state = {
optionsArray: [],
dropDownValueArray: [] //<---this needs to be an array and will
// hold the 'value' field listed in your
// optionsArray
}

Your componentDidMount() looks something like this:

componentDidMount(){
fetch(ROOT_URL + '/categories') //<-this is without tokenAuth etc.
.then(r => r.json()}
.then(categories => {
const existingCat = []
categories.map(cat => existingCat.push({key: cat.id value: cat.id text: cat.name }))
this.setState({ optionsArray: existingCat })
})

A note on the above. Notice that I used the category Id (cat.id) for the value. This is an integer (and more importantly type of number) and part of what I used to handle dividing out which categories where added by the user and which ones came from the database. This is where you would make your choice about how you’re going to filter your array later. Keep that in mind if you’re coding along and run into this question about how you tell the difference between user input and user selected values.

So the next two items work in conjunction. The problem statement is:

“How do we handle what the user types in and how do we get it to display as a selected option?”

The dropDownValue array is where all of the selected values live. When using state, your onChange={x} prop on the drop down handles the existing categories and should add them to this array. The values in the array are the values that are displayed in the dropdown and that is part of what you want your onAddItem to also manage. My onAddItem handler looks like:

handleOnAdd = (e) =>{
const newCat = { key: e.target.value, text: e.target.value,
value: e.target.value }
this.setState({
dropDownValueArray: [...this.state.value, newCat] <--use the
//spread to append the
//array or you could use .push()
})
}
// e.target.value is the text that they have typed in.

The handleOnChange() is similar but the way the event is passed in is odd with the scripting of the semantic component. I’m not sure if this has to do with the combination of a synthetic event (something I’m not 100% on yet) or my own coding, but in order to get at the actual value I wanted I needed to do this:

onChange={(e,optionsObj) => this.updateCategories(optionsObj.value)

optionsObj in the argument is the actual object in the options array. The value is it’s value property. In the options up above that would be the cat.id that is in value and not the one in it’s key.

So, now we have a user who can select from existing categories and add their own. All of their selections end up in the dropDownValueArray. In order to persist this and the associations I needed a way to differentiate between what the user typed in and what already exists in the database. I used typeof() method to filter the results.

Remember how I said it was important that the value of our existing category objects was an integer? Using:

const existingCategories = this.state.dropDownValueArray.filter(element => typeof(element) === "number")const newCategories = this.state.dropDownValueArray.filter(element => typeof(element) === "string")

I am able to get both the id’s of the existing categories and an array of strings to map over to create existing categories. You can do this in multiple ways, but I found that keeping the value types different since I already had them coming in was easiest. It also accounted for users inputting “1” or other numbers and characters as category names with out some regex gymnastics.

From this point on you should have the new user inputted data as well as existing data to associate. I used this on the back end to generate new categories with the text the user put in as the new category names and then associated both old and new categories with the note that the user wrote. Using this format and repopulating the existing category list after user submission, I’m then able to then use this category list throughout my project for filtering search results or other display options. This would mean you may want to move the fetch out of the componentDidMountto a higher component helper function so that you can call it in sibling components to get sortable fields on an index page or other places you may want them. Using Redux I’m able to get around much of the one way data flow, but that’s a post to come. Thanks for reading and if you have any comments or suggestions, please leave them below. Thanks and happy coding!

Will Ley

Written by

Will Ley

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade