Share Data between Riot Components with Riot-Mieosis (State Manager)

RMAG news

This article covers the creation of a state manager, to share data between multiple RiotJS Components

Before starting, make sure you have a base application running, or read my previous article Setup Riot + BeerCSS + Vite.

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/

It exists three methods for sharing data between components:

Use Riot properties (props) 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 an Event Emitter like Mitt, a messaging pattern called Pub/Sub, learn more on my previous article.

Last method is a state manager: A global state shared and accessible by all components. In other frontend frameworks, you may have heard about Pinia for Vuejs or Redux for React. For RiotJS, the state manager is riot-meiosis.

Riot Mieosis setup

First install the NPM package into your Riot/Vite project:

npm i –save @riot-tools/meiosis

Then create a store.js file:

import { RiotMeiosis } from @riot-tools/meiosis;

const state = {
firstname : John,
lastname : Wick,
firstnameEdit : ,
lastnameEdit : ,
email : ,
displayForm : false
}

/** Create the state manager instance */
const stateManager = new RiotMeiosis(state, { flushOnRead: false, statesToKeep: 1 });

/** Extract the state stream **/
const { stream } = stateManager;

/** Add Root state reducer: merge the old and new state objects */
stream.addReducer((newState, oldState) => {
// Object oldState { firstname: “John”, lastname: “Wick”, firstnameEdit: “”, lastnameEdit: “”, email: “”, displayForm: false }
// Object newState { displayForm: true }
return {
oldState,
newState
}
});

export default stateManager;

Source code: https://github.com/steevepay/riot-beercss/blob/main/examples/meiosis/store.js

Then import the store into your components, in our a Welcome Card with a fistname and lastname. The file is named c-welcome-card.riot:

<cwelcomecard>
<article class=no-padding border round>
<img class=responsive small top-round src=./examples/data/img-card.png>
<div class=padding>
<h5>Welcome</h5>
<p>Bonjour <b>{state.firstname} {state.lastname}</b> 👋 to our app! We’re excited to have you here.Whether you’re in finance, marketing, or operations, our app delivers the insights you need to drive growth and stay ahead of the competition.</p>
<nav>
<button onclick={ editProfile }>Edit Profile</button>
</nav>
</div>
</article>
<script>
import store from ./store.js;

const mapToState = (appState, ownState) => ({ ownState, appState });
export default store.connect(mapToState)({
editProfile () {
store.dispatch({ displayForm: true })
}
})

</script>
</c-welcome-card>

Source code: https://github.com/steevepay/riot-beercss/blob/main/examples/meiosis/c-welcome-card.riot

Code breakdown:

The store is loaded
A reducer is created to merge the component state with the global state: now the component can access the global state with this.state.
To connect the component to the global state, the Riot component must be wrapped with the store.connect() function, and pass the reducer as an argument.
To update one of the values of the global store, call the store.dispatch() function; it behaves like this.update().

For our example, the store is loaded into another independent component: a Dialog displayed to update the firstname and lastname, and a button to validate. The file is named c-form.riot:

<cform>
<cdialog active={ state.displayForm } oncancel={ close } onconfirm={ confirmEdit }>
<template slot=body>
<h5 style=margin-bottom:15px>Edit Profile</h5>
<p>{state.firstnameEdit} {state.lastnameEdit}</p>
<cinput value={ state.firstnameEdit } onkeydown={ (ev) => updateInput(ev, firstnameEdit) } onchange={ (ev) => updateInput(ev, firstnameEdit) } outlined=true round=true placeholder=Firstname />
<cinput value={ state.lastnameEdit } onkeydown={ (ev) => updateInput(ev, lastnameEdit) } onchange={ (ev) => updateInput(ev, lastnameEdit) } outlined=true round=true placeholder=Lastname />
</template>
</c-dialog>
<script>
/** State manager **/
import store from ./store.js;
/** Components **/
import cInput from ../../components/c-input.riot;
import cButton from ../../components/c-button.riot;
import cDialog from ../../components/c-dialog.riot;

const mapToState = (appState, ownState) => ({ ownState, appState });
export default store.connect(mapToState)({
components: {
cInput,
cButton,
cDialog
},
close() {
store.dispatch({ displayForm: false })
},
updateInput(ev, keyName) {
store.dispatch({
[keyName]: ev?.target?.value ??
})
console
},
confirmEdit() {
store.dispatch({
displayForm: false,
firstname: this.state.firstnameEdit,
lastname: this.state.lastnameEdit
})
}
});
</script>
</c-form>

Source code: https://github.com/steevepay/riot-beercss/blob/main/examples/meiosis/c-form.riot

Code details:

The dialog displays two inputs to update the firstname, and lastname.
If the confirm button is clicked, the new values are saved into the global state manager thanks to the store.dispatch. The welcome card receives the update; the first and last names are refreshed.

Now we can load both components into a common index.riot file:

<indexriot>
<div style=width:400px;padding:40px;>
<cwelcomecard />
<cform />
</div>
<script>

import cForm from ./c-form.riot
import cWelcomeCard from ./c-welcome-card.riot

export default {
components: {
cForm,
cWelcomeCard
}
}
</script>
</index-riot>

Source code: https://github.com/steevepay/riot-beercss/blob/main/examples/meiosis/index.riot

Conclusion

A state manager is quite powerful for reading and updating data across Riot Components. The data could be lists, objects, or Maps. The store can be connected to unlimited components for large production front-end applications made with RiotJS!

Leave a Reply

Your email address will not be published. Required fields are marked *