Share Data between Riot Components with Mitt (Event Emitter)
Tags: riotjs, javascript, beginners, frontend
Published: 2024-04-14 19:58:50

This article covers using Events with Mitt between multiple Riot components for sharing data.

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/

It exist three methods for sharing data between components:
* Use [Riot properties (props)](https://riot.js.org/documentation/#properties) to pass values to a child component. The child component must emit Events to the parent component if an action happens, such as a click or input change. **In this case, the communication scope is limited:** parent component to child and child to parent.
* Use a state manager, such as Pinia for Vuejs or Redux for React, to share a state across components/pages. **The communication scope is global:** all components can view and edit states.

The last method uses an Event Emitter like Mitt, a messaging pattern called Pub/Sub: one or many <u>publishers</u> emit the data, and the listener (<u>subscriber</u>) handles it without knowing which publishers. This article reproduces the pattern between multiple RiotJS components! The goal is to:
* **Step 1:** Create a base Riot application showing a Confirmation Modal component when clicking a button. The button is the publisher, and the Modal is the subscriber.
* **Step 2:** When the modal is visible and either validated or cancelled, it emits an event to the original publisher. In this case, the Modal is the publisher, and the Button is the subscriber.

![Animation of a Riot confirmation modal opening due to a Mitt event](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7994ebpc1hekdrwrw1xd.gif)

## Emitter Base

First install [Mitt](https://www.npmjs.com/package/mitt) into your Riot/Vite project:
```bash
$ npm install mitt
```

Create a file named **events.js** then load Mitt and instantiate it:
```js
/** EVENT BUS **/
import mitt from 'mitt'

const _bus = mitt();

export default _bus;
```

## Dialog Component (Subscriber)

Create a dialog component named **c-dialog.riot**, the base of the component is taken from my previous article [Create a Dialog component with RiotJS](https://dev.to/steeve/dialog-component-with-riotjs-material-design-1c40):
```html
<c-dialog>
    <dialog class="{state.active ? 'active ' : null}">
        <h5>{ state.title }</h5>
        <div>
            { state.message }
        </div>
        <nav class="right-align no-space">
            <button onclick={ toggleModal } class="transparent link">{ state.cancel }</button>
            <button onclick={ validateAction } class="transparent link">{ state.validate }</button>
        </nav>
    </dialog>
       <script>
       import events from './events.js'

        export default {
            state: {
                active: false,
                title: 'Confirmation',
                message: 'Are you sure?',
                callbackID : '',
                args : true,
                validate : 'Validate',
                cancel: "Cancel"
            },
            onMounted() {
                events.on('open-modal-validation', this.openModalValidation);
            },
            onUnmounted() {
                events.off('open-modal-validation', this.openModalValidation);
            },
            validateAction () {
                if (this.state.callbackID) {
                    events.emit(this.state.callbackID, this?.state?.args);
                }
                this.toggleModal();
            },
            openModalValidation ({ message, callbackID, args }) {
                this.update({
                    message,
                    callbackID,
                    args: args ?? true
                })
                this.toggleModal();
            },
            toggleModal() {
                this.update({ active: !this.state.active })
                if (this.state.active === true) {
                    console.log("Modal opened.")
                } else {
                    console.log("Modal closed.")
                }
            }
        }
    </script>
</c-dialog>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/examples/mitt/c-dialog.riot

Code breakdown:
* The modal is visible only if the **state.active** is `true`.
* The dialog can be customised with custom data, it is stored in the [State Riot](https://riot.js.org/documentation/#state) object
* The event bus is loaded with `import events from './events.js'`.
* When the component is mounted, it subscribes to the event `open-modal-validation` thanks to `events.on('open-modal-validation', this.openModalValidation);`. When the event named "open-modal-validation" occurs, it will execute the function `openModalValidation`.
* The **openModalValidation** function toggles the visibility of the modal by setting **state.active** to `true`. At the same time, it gather additional data such as a custom **state.message**, and custom **state.callbackID**.
* If the validation button is clicked, it emits an Event to the **state. callback** with custom arguments **state.args**; it can be an Object or String.
* If the cancel button is clicked, the modal is deactivated by setting **state.active** to `false`.

## Index Component (Publisher)

Finally, load the Dialog, and a Button into an **index.riot** file:
```html
<index-riot>
    <div style="width:600px;padding:20px;">
        <c-dialog />
        <c-button onclick={ confirmDeletion }> Delete File </c-button>
    </div>
    <script>
        import cButton from "../../components/c-button.riot";
        import cSnackbar from "../../components/c-snackbar.riot";
        import cDialog from "./c-dialog.riot";
        
        import events from "./events.js"

        export default {
            components: {
                cButton,
                cDialog
            },
            onMounted() {
                events.on('delete-file', this.deleteFile);
            },
            onUnmounted() {
                events.off('delete-file', this.deleteFile);
            },
            state: {
                active: false
            },
            confirmDeletion () {
                events.emit('open-modal-validation', { 
                    message: "Do you confirm you want to delete the file \"2024-04-invoice.pdf\"?",
                    callbackID: 'delete-file'
                });
            },
            deleteFile () {
                console.log("FILE DELETED! 🗑️");
            }
        }
    </script>
</index-riot>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/examples/mitt/index.riot

Code details:
1. Components are imported with `import cDialog from "./components/c-dialog.riot";` then loaded in the **components:{}** Riot object.
2. The `dialog` component is instantiated with `<c-dialog/>` on the HTML.
3. The event emitter, Mitt, is loaded with `import events from "./events.js"`
4. When clicking the button, the function **confirmDeletion** is executed and emits the event "open-modal-validation". The **Dialog is now visible!** Simultaneously, a custom message and the subscriber ID "delete-file" are passed as arguments for the event.
5. When the dialog button is clicked, an event is emitted, using the **state.callbackID** as the destination subscriber.

This method can be extended by creating more buttons (publishers) emitting to only one Dialog (subscriber), for instance:

![Three buttons are opening the same Dialog box to confirm deletions actions, either: a file, a bucket or an account](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfbmtorlvaa7421od24t.gif)

Here is the code of the **index.riot** to replicate the GIF demo:
```html
<index-riot>
    <div style="width:600px;padding:50px;">
        <c-dialog />
        <c-button onclick={ confirmDeletion }> Delete File </c-button>
        <c-button onclick={ confirmDeletionBucket }> Delete Bucket </c-button>
        <c-button error={ true } onclick={ confirmDeletionAccount }> Delete Account </c-button>
    </div>
    <script>
        import cButton from "../../components/c-button.riot";
        import cSnackbar from "../../components/c-snackbar.riot";
        import cDialog from "./c-dialog.riot";
        
        import events from "./events.js"

        export default {
            components: {
                cButton,
                cDialog
            },
            onMounted() {
                events.on('delete-file', this.deleteFile);
                events.on('delete-bucket', this.deleteBucket);
                events.on('delete-account', this.deleteAccount);
            },
            onUnmounted() {
                events.off('delete-file', this.deleteFile);
                events.off('delete-bucket', this.deleteBucket);
                events.off('delete-account', this.deleteAccount);
            },
            state: {
                active: false
            },
            getRandomNumber() {
                return Math.floor(Math.random() * Date.now())
            },
            confirmDeletion () {
                events.emit('open-modal-validation', { 
                    message: "Do you confirm you want to delete the File \"2024-04-invoice.pdf\"?",
                    callbackID: 'delete-file',
                    args: "file-" + this.getRandomNumber()
                });
            },
            confirmDeletionBucket() {
                events.emit('open-modal-validation', { 
                    message: "Are you sure you want to delete this S3 bucket \"2024-assets\"? This action is irreversible.",
                    callbackID: 'delete-bucket',
                    args: "bucket-" + this.getRandomNumber()
                });
            },
            confirmDeletionAccount() {
                events.emit('open-modal-validation', { 
                    message: "Are you sure you want to proceed with deleting your account? This action is irreversible and will permanently remove all your personal data associated with the account.",
                    callbackID: 'delete-account',
                    args: "account-" + this.getRandomNumber()
                });
            },
            deleteFile (id) {
                console.log("%c File deleted! ID: " + id, 'background: #222; color: #bada55');
            },
            deleteBucket (id) {
                console.log("%c Bucket deleted! ID: " + id, 'background: #222; color: #ff00ff');
            },
            deleteAccount (id) {
                console.log("%c Account deleted! ID: " + id, 'background: #222; color: #ff0000');
            }
        }
    </script>
</index-riot>
```
> Source Code: https://github.com/steevepay/riot-beercss/blob/main/examples/mitt/index.riot

Code breakdown:
* On the [onMounted](https://riot.js.org/api/#component-object) Riot Hook, three Events are listening and redirecting to three function: deleteFile, deleteBucket, or deleteAccount. In real world production application, it could be an API call to delete a resource (file, or element on a Database)
* Three buttons are created on the HTML, and each click emits the event **open-modal-validation** and passes a custom message, callback ID, and argument:
   1. The `callbackID` is an Event to emit when the validation button is clicked.
   2. The `args` is used to pass a resource ID from an API or Database, like a file ID or an Account ID. In our case, a random ID is passed as an argument.
* When the Dialog is validated, it emits **callbackID** as an Event, and the corresponding function is executed to delete the file, bucket or account.
* **When the Component is removed from the web page, any event must be listened:** When the [onUnmounted](https://riot.js.org/api/#component-object) Riot hook is executed, the **event.off** removes the Event subscription.

## Conclusion

Instead of creating one Dialog for each button, only one Dialog is required for many buttons with the pub/sub pattern. Now, a component can listen to and emit events to communicate data based on actions. Voilà 🎉 

Have a great day! Cheers 🍻