Snackbar Component with RiotJS (Material Design)
Tags: riotjs, javascript, beginners, frontend
Published: 2024-04-04 18:10:51

This article covers creating a Riot Snackbar component, using the Material Design CSS BeerCSS, and executing an action on input and select events.

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/

Snackbars communicate messages at the bottom of the screen, that are minimally interruptive and don’t require user action. They can contain a single action, such as "Undo", "Open", or "Learn more".

![Example of snackbar showing on a mobile app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nrli31lo13nbf486jegt.png)

## Snackbar Component Base

The goal is to create a Riot app with a Snackbar appearing when a button is clicked, and dismissing it automatically when the action is clicked.

![Gif of a Snackbar made with RiotJS displayed when a button is click](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nntmy8q4ur4mp8t5e8j2.gif)

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

Write the following code in `./components/c-snackbar.riot`. The HTML comes from the [BeerCSS documentation](https://www.beercss.com/#snackbars) and I added [RiotJS syntax](https://riot.js.org/documentation/#syntax) for the logic:
```html
<c-snackbar>
    <div class="
            snackbar
            { props?.active ? 'active ' : null }
            { props?.top ? 'top ' : null }
            { props?.bottom ? 'bottom ' : null }
            { props?.error ? 'error ' : null }
            { props?.primary ? 'primary ' : null }
            { props?.secondary ? 'secondary ' : null }
            { props?.tertiary ? 'tertiary ' : null }
        ">
        <i if={ props?.icon }>{props.icon}</i>
        <span class="max"><slot></slot></span>
        <a if={ props?.action } onclick={ clicked } class="inverse-link">{ props?.action }</a>
    </div>
    <script>
        export default {
            clicked (e) {
                e.preventDefault();
                e.stopPropagation();
                this.root.dispatchEvent(new Event("action"));
            }
        }
    </script>
</c-snackbar>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/components/c-snackbar.riot

Let's break down the code:
1. The `<c-snackbar>` and `</c-snackbar>` define a custom root tag, with the same name as the file. You must write it; otherwise, it may create unexpected results. Using the `<div></div>` as a root tag or redefining native HTML tags is a bad practice, starting with `c-` is a good convention.
2. The message is passed as [Slot tag](https://riot.js.org/api/#slots) `<slot></slot>`, a Riot.js core feature to inject custom HTML templates in a child component from its parent. In our case, only a String is injected, without HTML.
3. Snackbars can display a <u>single text button that lets users take action</u> on a process performed by the app: If the attribute **props.action** exists, the message as String is printed inside the action button. 
4. <u>When the action button is clicked: </u> The event **click** is caught and executes the function `clicked` to emit a custom event named **action**.
5. For displaying the snackbar, it must contain the `active` class: when the attribute **props.active**, it adds the class `active` thanks to the expression: `{ props?.active ? 'active ' : null }`.
6. Different styles are available for the component, for instance, `primary` is applied if the **props?.primary** exists and is true.

Finally, load and instantiate the **c-snackbar.riot** in a front page named **index.riot**:
```html
<index-riot>
    <div style="width:600px;padding:20px;">
        <c-button onclick={ () => openSnack("default") } inverse={ true }>Default</c-button>
        <c-button onclick={ () => openSnack("error") } error={ true }>Error</c-button>
        <c-button onclick={ () => openSnack("primary") } primary={ true }>Primary</c-button>

        <c-snackbar 
            active={ state.active }
            onaction={ close }
            error={ state.error } 
            icon={ state.icon } 
            action={ state.action }
            primary={ state.primary }
        >
            { state.message }
        </c-snackbar>
    </div>
    <script>
        import cButton from "../components/c-button.riot"
        import cSnackbar from "../components/c-snackbar.riot"

        export default {
            components: {
                cSnackbar,
                cButton
            },
            state: {
                active: false,
                icon: null,
                message: null,
                error: null,
                timeout: null
            },
            openSnack(type) {
                if (this.state.active === true) {
                    return;
                }
                this.update({ 
                    active: true,
                    message: type === 'error' ? "Something went wrong." : "Email Archived.",
                    action: type === 'error' ? "Contact Support" : "Undo",
                    icon: type === 'error' ? 'error' : 'check',
                    error: type === 'error',
                    primary: type === 'primary'
                })
                clearTimeout(this.state.timeout)
                this.state.timeout = setTimeout(() => {
                    this.update({ active: false })
                }, 3000)
                this.update();
            },
            close () {
                this.update({ active: false })
            }
        }
    </script>
</index-riot>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/examples/index.snackbar.riot

Code details:
1. The component is imported with `import cSnackbar from "./components/c-snackbar.riot";` then loaded in the **components:{}** Riot object. A custom [Button Riot component](https://dev.to/steeve/button-component-with-riotjs-material-design-174p) is also loaded into the page: when a click happen, it will show the toaster with a custom message!
2. The `snackbar` component is instantiated with `<c-snackbar />` on the HTML.
3. The component's state, such as `active`, is stored in the **state:{}** Riot object under the `state.active` Boolean property. The property is passed as an attribute, such as: `<c-snackbar active={ state.active } />`. To make the toaster fully interactive, further states are stored: the `message`, the `icon` and the `action` label.
4. When a Button is clicked, the function `openSnack` is executed thanks to `onclick={ () => openSnack("default") }`. A type is passed as first argument to select the type of snackbar, the message, the icon and the action. At the end, the toaster is displayed by updating **state.active** to true.
5. To make the toaster disappear automatically, a [timeout](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout) is created to hide the Snackbar by updating **state.active** to **false** after 2s.
6. The custom Event **action** is watched with `onaction`: if a click fires the Snackbar' action, the function **close** updates **state.active** to dismiss it.

## Snackbar Component Testing

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

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

Have a great day! Cheers 🍻