Input Component with RiotJS (Material Design)
Tags: riotjs, javascript, beginners, frontend
Published: 2024-03-20 16:28:27

This article covers how to create an [Riot](https://riot.js.org/) input component, using the Material Design CSS [BeerCSS](https://www.beercss.com/). 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 paired with BeerCSS, 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/

BeerCSS provides many input states, such as "error", "icons", or "loading", and it supports different stylings, such as "round", "small", and "medium". The goal is to create one Input component that dynamically supports all stylings and listens to value input. 

![Screenshot of the BeerCSS input documentation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wavds13q0hn4hgazbiak.png)

## Base input component

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

Into `./components/c-input.riot`, write the minimum HTML and Javascript code for an input:
```html
<c-input>
    <div class="field border">
        <input type="text" value="{ props?.value }">
    </div>
</c-input>
```
Let's break down the code:
* The `<c-input>` and `</c-input>` defined a custom root tag, with the same name as the file. You must write it; otherwise, it may create unexpected results. Using only `<input>` or redefining native HTML tags is a bad practice, so starting `c-` is a good naming.
* The child `div` tag is copied from BeerCSS input examples.
* To inject a custom value into the input, you must use **value="{ props?.value }"**: The value is passed as `props`: With props we can pass data to the components via custom attributes to the component tag.

Then we can load and instantiate the `c-input.riot`, into a front page:
```html
<index-riot>
    <div style="width:500px;padding:20px;">
        <h4 style="margin-bottom:20px">Riot + BeerCSS</h4>
        <c-input value={ state.firstname } onchange={ updateValue } onkeydown={ updateValue }></c-input>
    </div>
    <script>
        import cInput from "../components/c-input.riot";

        export default {
            components: {
                cInput
            },
            state: {
                firstname: 'Default name'
            },
            updateValue (ev) {
                this.update({
                    firstname: ev.target.value
                })
            }
        }
    </script>
</index-riot>
```
Code break-down:
1. The component is imported with `import cInput from "../components/c-input.riot";`, then loaded into the `components` Riot object.
2. On the HTML, the Input component is instantiate with `<c-input value={ state.firstname } onchange={ updateValue } onkeydown={ updateValue }></c-input>`
3. Three attributes are passed 
   * **value**: it will be the input value
   * **onchange** and **onkeydown**: These are events emitted if the input value changes or a key is pressed. When an event is fired, it executes a function, in our case `updateValue`.
4. The default value of the `firstname` is "Default name".

**To store the input value**, a state named `firstname` is required, and it must be updated thanks to the function named `updateValue`. If the input value changes, the `firstname` will take the new input value, and then propagate it into `props`. 

Writing a dedicated function to update `state.firstname` is not required, it can be simplified into one line:
```html
<c-input value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeydown={ (ev) => update({ firstname: ev.target.value }) }></c-input>
```

## Add input component styles

An input can print multiple styles: normal, helper, errors, icons, loading, or all combined.

Let's add the helper 
```html
<c-input>
    <div class="field border">
        <input type="text" value={ props?.value ?? '' }>
        <span class="helper" if={ props?.helper }>{ props.helper }</span>
    </div>
</c-input>
```
The helper HTML is coming from BeerCSS, then I just added two elements:
* `{ props.helper }` It will print the helper value through props (HTML attribute).
* `if={ props?.helper }` condition to display or hide the helper if a value exists

Let's try the helper by defining the attribute "helper" with a string, such as:
```html
<c-input helper="This is a helper" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeydown={ (ev) => update({ firstname: ev.target.value }) }></c-input>        
```
The result:

![Riot input component with a helper as attribute](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9n2xwx66zft4hqt6wj9o.png)
If the helper attribute is empty, it will hide the helper HTML tags.

The expression is the same for a label and error state:
```html
<c-input>
    <div class="
        field border
        { props?.label ? ' label' : '' }
    ">
        <input type="text" value={ props?.value }>
        <label if={ props?.label }>{ props.label }</label>
        <span class="helper" if={ props?.helper && !props?.error }>{ props.helper }</span>
        <span class="error" if={ props?.error }>{ props.error }</span>
    </div>
</c-input>
```
Code Breakdown:
* Riot support writing conditions into attributes values, in our case we are adding the class **label** if the `props.label` exists.
* The label tag and value is displayed if the `props.label` exists.
* The error is printed only if the `props.error` exists. It hides the `helper` because printing the error under the input is more important than an information `props.helper`.

![Riot Input Component with an error state](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/290cp4dr109idqfxb57m.png)

We can follow the same logic to support the loading state, and icons as prefix (props.input) and suffix (props.inputend):
```html
<c-input>
    <div class="
        field border
        { props?.error ? " invalid" : '' }
        { props?.label ? " label" : '' }
        { props?.icon || props?.loading ? " prefix" : '' }
        { props?.iconend || props?.loadingend  ? " suffix" : '' }
    ">
        <progress if={ props?.loading } class="circle"></progress>
        <i if={ props?.icon && !props?.loading }>{ props?.icon }</i>
        <input type="text" value={ props?.value }>
        <label if={ props?.label }>{ props.label }</label>
        <i if={ props?.iconend && !props?.loadingend }>{ props?.iconend }</i>
        <progress if={ props?.loadingend } 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-input>
```
Many front-end applications need different input types, such as numbers, passwords, and more. Let's create a props to support all input types:
```html
<input type={props?.type ?? 'text'} value={ props?.value }> 
```
The default input type is *text*, and it can also support: **number**, **password**, **file**, **color**, **date**, **time**.

**The component is almost done;** let's support multiple sizes and shapes (like round, fill, small, etc..). We have to add class conditionally to the input:
```js
        { props?.round ? " round" : ''}
        { props?.fill ? " fill" : ''}
        { props?.small ? " small" : ''}
        { props?.medium ? " medium" : ''}
        { props?.large ? " large" : ''}
        { props?.extra ? " extra" : ''}
```

Here is the final Input component code:
```html
<c-input>
    <div class="
        field border
        { props?.round ? " round" : ''}
        { props?.fill ? " fill" : ''}
        { props?.small ? " small" : ''}
        { props?.medium ? " medium" : ''}
        { props?.large ? " large" : ''}
        { props?.extra ? " extra" : ''}
        { props?.error ? " invalid" : '' }
        { props?.label ? " label" : '' }
        { props?.icon || props?.loading ? " prefix" : '' }
        { props?.iconend || props?.loadingend  ? " suffix" : '' }
    ">
        <progress if={ props?.loading } class="circle"></progress>
        <i if={ props?.icon && !props?.loading }>{ props?.icon }</i>
        <input type={props?.type ?? 'text'} value={ props?.value }>
        <label if={ props?.label }>{ props.label }</label>
        <i if={ props?.iconend && !props?.loadingend }>{ props?.iconend }</i>
        <progress if={ props?.loadingend } 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-input>
```

Finally we can instantiate on the `index.riot` a couple of `<c-inputs>` to try all attributes:
```html
<c-input label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
        
<c-input helper="This is a helper" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>        

<c-input error="Something is wrong" helper="This is a helper" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>        
        
<c-input icon="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
        
<c-input iconend="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
        
<c-input icon="search" iconend="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>

<c-input loading="true" icon="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
        
<c-input loadingend="true" iconend="edit" label="Firstname" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>

<c-input fill="true" round="true" small="true" label="Test" value={ state.firstname } onchange={ (ev) => update({ firstname: ev.target.value }) } onkeyup={ (ev) => update({ firstname: ev.target.value }) }></c-input>
```
Here is the result:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/18uzqr7m14n4f6hkkxcv.png)

Find the source code on the following link:
https://github.com/steevepay/riot-beercss

## Input Testing

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

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

Have a great day! Cheers 🍻