Select Component with RiotJS (Material Design)
Tags: riotjs, frontend, javascript, beginners
Published: 2024-03-28 07:53:04

This article covers creating a Riot Select component, using the Material Design CSS BeerCSS. Before starting, make sure you have a base application running, or read my previous article Setup [Riot + BeerCSS + Vite](https://dev.to/steeve/riotjs-material-design-how-to-setup-beercss-269c).

> These articles form a series focusing on [RiotJS](https://riot.js.org/) paired with [BeerCSS](https://www.beercss.com/), designed to guide you through creating components and mastering best practices for building production-ready applications. I assume you have a foundational understanding of Riot; however, feel free to refer to the documentation if needed: https://riot.js.org/documentation/

**A Select provides a menu of options:** The goal is to create a Select component with BeerCSS design and listen to change events.

![Select Elements made with BeerCSS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oeiodbukvjq3xjq7wv6.png)

## Select Component Base

First, create a new file named `c-select.riot` under the components folder. The `c-` stands for "component", a useful naming convention and a good practice.

Write the following HTML code (found on the BeerCSS documentation) in `./components/c-select.riot`:
```html
<c-select>
    <div class="field suffix 
                {props?.label ? ' label' : null }
                {props?.error ? ' invalid' : null }
            ">
        <select>
            <option if={ !props?.loading }  each={option in props.options} value={ option } selected={ option === props.value ? true : null }>{ option }</option>
        </select>
        <label if={ props?.label }>{ props.label }</label>
        <i if={ !props?.loading && !props?.img  && !props?.icon} >arrow_drop_down</i>
        <i if={ !props?.loading && !props?.img  && props?.icon} >{ props.icon }</i>
        <img if={ !props?.loading && props?.img } class="circle" src={ props.img }>
        <progress if={ props?.loading } class="circle"></progress>
        <span class="helper" if={ props?.helper && !props?.error }>{ props.helper }</span>
        <span class="error" if={ props?.error }>{ props.error }</span>
    </div>
</c-select>
```
Let's break down the code:
1. The `<c-select>` and `</c-select>` define a custom root tag, with the same name as the file. You must write it; otherwise, it may create unexpected results. Using the `<label>` as a root tag or redefining native HTML tags is a bad practice, so starting `c-` is a good naming.
2. The list of options passed as attributes is an Array of String, for instance `["red", "green", "blue" ]`, and it is looped thanks to the [each](https://riot.js.org/documentation/#loops) Riot attribute as following: `each={option in props.options}`. A new `<option>` tag is created for each element of the list.
3. For each element of the list, two important attributes are defined on the `<option>` tag:
   * The *value* attribute gets the list element value, either `red`, `green` or `blue`. The `change` event will emit the value if the option is selected.
   * The **selected** attribute defines whether the `props. value` is equal to the option's value. 
4. The component has a loading state: if the `props?.loading` exists, option tags, icon and image are hidden with `if={ !props?.loading }`; finally, it will display a loading icon.
5. The select has a default icon **arrow_drop_down**, and it is possible to overwrite it with another [Google Material Icon](https://fonts.google.com/icons) by passing the props `icon`. The icon is displayed only if there is no image, and no loading state. 
6. Instead of an icon for the select element, an image with the attribute `img` can be included. The image is not printed if the loading property is activated.
7. To help the user, a `helper` property can be added; it will show a message below the select element. If the `props?. helper` exists, the `helper` class is added to the component.
8. If an error occurs, it is possible to display an error message below the select input thanks to the `error` attribute. If the `props.error` exists, the `error` class is added to the component.

Finally, load and instantiate the **c-select.riot** in a front page named **index.riot**:
```html
<index-riot>
    <div style="width:600px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>
        <c-select label="Color" options={ state.options } value={ state.value } onchange={ changed } />
        <c-select img="./favicon.png" helper={ "Color selected: " + state.value } label="Color" options={ state.options } value={ state.value } onchange={ changed }/>
    </div>
    <script>
        import cSelect from "./components/c-select.riot"

        export default {
            components: {
                cSelect
            },
            state: {
                options: [
                    'red',
                    'green',
                    'blue',
                    'purple'
                ],
                value: 'green'
            },
            changed (ev) {
                this.update({ value: ev.target.value })
            }
        }
    </script>
</index-riot>
```
Code details:
1. Components are imported with `import cSelect from "./components/c-select.riot";` then loaded in the **components:{}** Riot object.
2. For testing, the component cSelect is instantiated two times with `<c-select/>` on the HTML. The second select takes an image with the `img` attribute.
3. The select component takes the list of options in the **option** attribute with **options={ state.options }**.
4. The selected value is stored in the **State** Riot object **state: { value: 'green' }**. The default value selected is green.
5. If an option is selected, the `change` events is fired, and the `changed` function is executed to update the `state.value`.
6. To update a component state, the `this.update()` Riot function must be used. In our case the `state.value` gets the event value, such as: `this.update({ value: ev.target.value })`.

Here is the generated HTML:
![Two select components made with RiotJS with a list of strings as option](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4xigr2ywok4ym1vt1cj.png)

## Select Component Advanced

A production front-end often gets a list of objects coming from a Database/API, including:
* An ID as a number or UUID as a String for the option's value.
* A different String for the option's label.

As an example, let's provide a list of cities to the select component:
```js
export default {
  state: {
    list: [
       { id: 0, city: "Paris" },
       { id: 1, city: "London"},
     { id: 2, city: "Berlin"},
     { id: 3, city: "New York"}
    ]
  }
}
```

The current Select component takes only a list of Strings, and changes must be made to support a list of Objects. Let's add the following HTML to the `c-select.riot` component:
```html
<option each={option in props.options} value={ option[props.itemValue] }  key={ option[props.itemValue] } selected={ option[props.itemValue] === props.value ? true : null } if={ props.itemValue && !props?.loading }>{ option[props?.itemLabel || props?.itemValue] }</option>
```
Details of the code:
* To loop through all objects, the **each** Riot attribute is used `each={option in props.options}`.
* To assign a value based on an object, the property `item-value (props.itemValue)` defines the key assigned to the `value` attribute. To decompose the expression **value={ option[props.itemValue] }**:
   * The `option` is an item from the list
   * Passing the key `props.itemValue` under square brackets returns the object's value.
* To assign a label based on an object, the property `item-label (props?.itemLabel)` defines the key assigned to the option's label. If the `itemLabel` does not exist, it takes the `itemValue` as a label by default.
* **Notice the key={ option[props.itemValue] }:** Adding the `key` Riot attribute to the looped tags provides a more precise strategy for tracking the item’s position. If the lists are immutable, this will greatly improve the loop performance.
* Finally, the option is printed only if the `itemValue` property exists and the component is not loading.
* **Bonus:** a Google Material Icon is passed named "explore".

Let's change the `index.riot` and provide a list of objects to the Select component:

```html
<index-riot>
    <div style="width:600px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>
        <c-select icon="explore" helper={ "City selected: " + state.city } label="Cities" options={ state.list } value={ state.city } item-value="id" item-label="city" placeholder="Select a city" onchange={ changed }/>
    </div>
    <script>
        import cSelect from "./components/c-select.riot"

        export default {
            components: {
                cSelect
            },
            state: {
                list: [
                    { id: 0, city: "Paris" },
                    { id: 1, city: "London"},
                    { id: 2, city: "Berlin"},
                    { id: 3, city: "New York"}
                ],
                city: 3
            },
            changed (ev) {
                this.update({ city: parseInt(ev.target.value)})
            }
        }
    </script>
</index-riot>

```
Code breakdown:
1. The list of cities is passed to the Select component with `options={ state.list }`.
2. The `item-value="id"` attribute defines the key of the object used for the option's value.
3. The `item-label="city"` attribute defines the key of the object used for the option's label.
4. The `state.city` value is updated when a change event is fired: it will execute the `changed` function. `parseInt` is required because the event value is a string.

The generated HTML:
![Select component made with RiotJS with a list of object for options](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/taz5gqqgxrqywwxsu56z.png)

## Select Component Testing

It exists two methods for testing the Select component, and it is covered in two different articles:
* [Test with Vitest and Riot-SSR in a Node Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-node-env-hk0)
* [Test with Vitest in a JsDom Environment](https://dev.to/steeve/riot-component-unit-test-with-vitest-jsdom-env-182l)

## Conclusion

Voilà 🎉 We created a Select Riot Component using Material Design elements with BeerCSS. 

The source code of the select is available on Github:
https://github.com/steevepay/riot-beercss/blob/main/components/c-select.riot

Feel free to comment if you have questions or need help about RiotJS.

Have a great day! Cheers 🍻