Checkbox component with RiotJS (Material Design)
Tags: riotjs, frontend, javascript, beginners
Published: 2024-03-24 11:28:05

This article covers creating a Riot Checkbox 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/

Four checkbox states exist: unchecked, checked and disabled, and mixed (see the following screenshot). The goal is to create a checkbox Riot component with BeerCSS design and listen to click events.

![Screenshot of BeerCSS Checkboxes ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hemo8y9s7rtsx8vxf7ci.png)

## Checkbox Component Base

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

Into `./components/c-checkbox.riot`, write the following HTML (found on the BeerCSS documentation):

```html
<c-checkbox>
   <label class="checkbox" onclick={ inputClick } >
        <input type="checkbox" value={ props?.value ? true : false  } checked={ props?.value } disabled={ props?.disabled }>
        <span>{ props.label }</span>
    </label>
</c-checkbox>
```
Let's break down the code:
* The `<c-checkbox>` and `</c-checkbox>` defined 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.
* To enable the `checked` attribute, the `props.value` must exist and be **true**.
* The default value of a checkbox input is "on" as a String. It's not handy to manipulate in a Javascript application. Using the expression `value={ props?.value ? true : false  }` make sure the checkbox value is always a Boolean, either true or false.
* On the Web Standards for a checkbox, the **value** and **checked** are two different attributes; the component unifies the input and checked values.
* Thanks to the `props.disabled`, the `disabled` attribute is conditionally assigned to the Input tag to disable the checkbox.
* If the **label** exists, it is injected into: `<span>{props.label}</span>`.

W3C states that a boolean property is true if the attribute is present — even if the value is empty or false. [As mentioned in the documentation](https://riot.js.org/documentation/#boolean-attributes), Riot.js automatically fixes this behaviour: Boolean attributes (checked, selected, etc.) are ignored when the expression value is **false**:
```html
<input type="checkbox" checked={ null }> becomes <input type="checkbox">
<input type="checkbox" checked={ '' }> becomes <input type="checkbox">
<input type="checkbox" checked={ false }> becomes <input type="checkbox">
```
In case the expression is **true** they will be correctly rendered according to the specs:
```html
<input type="checkbox"  checked={ true }> becomes <input type="checkbox" checked='checked'>
<input type="checkbox"  checked={ 1 }> becomes <input type="checkbox"  checked='checked'>
<input type="checkbox"  checked={ 'is-valid' }> becomes <input type="checkbox"  checked='checked'>
```

Finally, the **c-checkbox.riot** can be instantiated into a front page **index.riot**:
```html
<index-riot>
    <div style="width:600px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>
        <c-checkbox onclick={ clicked } value={ state.value } label={ state.value }/>
        <c-checkbox onclick={ clicked } label="Disabled" disabled={ true } />
        <c-checkbox onclick={ clicked } label="Disabled" disabled={ true } value="true" /> 
    </div>
    <script>
        import cCheckbox from "./components/c-checkbox.riot";

        export default {
            components: {
                cCheckbox
            },
            state: {
                value: true
            },
            clicked (ev) {
                if (ev.target.tagName === "SPAN") {
                    ev.stopPropagation()
                    ev.preventDefault();
                    this.update({ value: !this.state.value })
                }
            }
        }
    </script>
</index-riot>
```
Code break-down:
1. The component is imported with `import cCheckbox from "./components/c-checkbox.riot";` then loaded into the **components:{}** Riot object.
2. On the HTML, the Button component is instantiated with         `<c-checkbox onclick={ clicked } />`
3. The state of the checkbox is stored in state Riot object `state: { value: false }`. **False** is the default value.
4. The **click** event is watched: when the event **click** is fired, the function **clicked** is executed.
5. On click, the **value** is updated to its opposite with `this.update({ value: !this.state.value })`
6. An important issue occurs: the event click is emitted twice! The expression `if (ev.target.tagName === "SPAN")` is used to accept only one event, then the propagation of the even is stopped thanks to `ev.stopPropagation();` and `ev.preventDefault();`.

Screenshot of the generated HTML:

![Four different checkbox components: default with label, disabled, disabled and checked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4t8yevqbh87nqmaj5ons.png)

## Fix the checkbox issue: stop the double-click event

As mentioned in the previous section, the **click** event is fired twice. The issue is that clicking the label triggers a click on both the `<c-checkbox>` and the child checkbox input `<input type="checkbox">`.

The solution is to stop the event propagation inside the component, and re-emit the event once. During this moment, I take the opportunity to change the Boolean value to its opposite: the parent HTML will receive a change event with the correct value:
* If the input is checked, the change event emits **true**.
* If the input is unchecked, the change event emits **false**.

The **c-checkbox.riot** updated:
```html
<c-checkbox >
    <label class="checkbox" onclick={ inputClick }>
        <input type="checkbox" value={ props?.value ? true : false  } checked={ props?.value } disabled={ props?.disabled }>
        <span>{ props.label }</span>
    </label>
    <script>
        export default {
            inputClick (e) {
                e.preventDefault();
                e.stopPropagation();
                this.root.value = this.props.value === true || this.props.value === "true" ? false : true;
                this.root.dispatchEvent(new Event('click'));
                this.root.dispatchEvent(new Event('change'));
            }
        }
    </script>
</c-checkbox>
```
Code breakdown:
* If a click occurs on the `<label>`, the click event is not propagated and cancelled, thanks to `e.preventDefault();` and `e.stopPropagation();`
* The value of the checkbox input takes its opposite.
* The **click** and **change** events are re-emitted thanks to the `dispatchEvent`.

The value update on the parent component **index.riot** can be simplified:
```html
<index-riot>
    <div style="width:600px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>
        <c-checkbox onclick={ clicked } value={ state.value } label={ state.value }/>
    </div>
    <script>
        import cCheckbox from "./components/c-checkbox.riot";

        export default {
            components: {
                cCheckbox
            },
            state: {
                value: false
            },
            clicked (ev) {
                this.update({ value: ev.target.value })
            }
        }
    </script>
</index-riot>
```
Now the **state.value** takes the value from the <u>click Event</u>, and the value always mirrors the current state of the checkbox. 

**Tips to simplify even more:** It is not required to create a "clicked" function, one line is enough to update the value:
```html
<c-checkbox onclick={ (ev) => update({ value: ev.target.value }) } value={ state.value } label={ state.value }/>
```

## Checkbox Component Testing

It exists two methods for testing the Checkbox 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 Checkbox Riot Component using Material Design elements with BeerCSS. 

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

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

Have a great day! Cheers 🍻