Composi 3.1.0 has two new classes: DataStore and DataStoreComponent. The are used together to provide stateless class components with a dataStore for separate state management.

In most cases your class components will be stateful. This enables making components that own local state. However, you could create a class component without state and pass data to it through its update method. Then you would use Redux or Mobx to manage state and update the component. These two solutions required a lot of boiler plate.

DataStores provides a simpler solution. You create a dataStore to own state. Then you define a dataStore component and during initialization you pass it the dataStore. DataStores use an observer to dispatch an event along with its updated state. When you pass a dataStore to a dataStore component, the component sets up a watcher to listen for the dataStore’s update event. When that happens, the component passes the dataStore’s current state to its own update method. Basically, the component re-renders itself with the data dispatched by the dataStore.

DataStore and DataStoreComponent reside in a folder called data-store . So, when you import them, you need to tag that folder name on to composi :

import { h } from 'composi'

import { DataStore, DataStoreComponent } from 'composi/data-store'

Notice the path for DataStore in the above import. That’s important in order to reach where these are stored. This keeps them out of the default import of Composi, resulting in a smaller file size. If you want to use these, you need to import them from Composi’s data-store folder as shown above.

Creating a DataStore

To use these you need to define a dataStore and a dataStore component. Let’s start with a dataStore. This takes one argument — the data you want to use for state. The data needs to be in a format that the dataStore can use. The format is an object literal with one property: state. Below we show how to create a new dataStore:

import { h } from 'composi'

import { DataStore, DataStoreComponent } from 'composi/data-store' const dataStore = new DataStore({

state: {

items: ['apples', 'oranges', 'bananas']

}

})

We can examine the state of our new dataStore from its state property:

console.log(dataStore.state)

// returns: {

// items: ['apples', 'oranges', 'bananas']

// }

DataStores has one public method for modifying their state: setState . This works exactly the same as the method with the same name on comonents. It takes a callback as its argument. And this callback gets passed the dataStore’s previous state as its argument. You can modify that state and then return it. By returning it you cause the dataStore to dispatch and event along with the updated state.

Let’s look at modify a dataStore’s state:

import { h } from 'composi'

import { DataStore, DataStoreComponent } from 'composi/data-store' // Create a dataStore:

const dataStore = new DataStore({

state: {

items: ['apples', 'oranges', 'bananas']

}

}) // Add a new item to the dataStore:

dataStore.setState(prevState => {

// Notice how we access the items array on prevState:

prevState.items.push('grapes')

// Don't forget to return prevState!

return prevState

}) // Delete an item from the dataStore:



dataStore.setState(prevState => {

// Remove oranges from items:

prevState.items.splice(1,1)

// Don't forget to return prevState!

return prevState

})

Creating a DataStore Component

So we have a dataStore, but by itself it isn’t very useful. In our example above, each time we changed the dataStore’s state, it fired an event. But since nothing nows about that event, nothing happens.

DataStore Component react to changes in a dataStore. So let’s create a dataStore component. DataStore components are extensions of Composi Component class. Whatever you can do with a Component class component, you can do with a dataStore component. The only difference is that you will never give a dataStore component state. Instead, you will give it a dataStore property pointing to the dataStore it will use:

import { h } from 'composi'

import { DataStore, DataStoreComponent } from 'composi/data-store' // Create a dataStore:

const fruitDataStore = new DataStore({

state: {

items: ['apples', 'oranges', 'bananas']

}

}) // Define a dataStore component:

class List extends DataStoreComponent {

render(data){

return (

<ul>

{data.items.map(item => <li>{item}</li>

}

</ul>

)

}

} // Create instance of dataStore component class.

// We need to provide a container to render in,

// and a dataStore to use:

const list = new List({

container: 'section',

dataStore: fruitDataStore

}) /* Because this component is stateless, we need to trigger its first render. We do that by passing the dataStore data to the component's update method: */

list.udpate(dataStore.state)

Now, if we change the state of the dataStore by adding or removing an item, the component list will update automatically. The above example was overly simplistic. Below is a Codepen example that is more advanced. In it we create an actions object to handle changes to the dataStore. The dataStore component’s events use those to update the dataStore’s state.

Observer Class

With the addition of DataStore and DataStoreComponent, Composi also adds on last new class: Observer. This is used by both DataStore and DataStoreComponent to work together for reactivity. When you create a new dataStore, it creates an observer that dispatches an event and the current state of the dataStore. The event is dataStoreStateChanged . When you pass the dataStore to a new dataStore component, during initialization the component create a watcher on that dataStore’s observer. That watcher listens for the dataStoreStateChanged and when it occurs, it executes a callback that calls the component update method with the state passed along with that event.

Observers have only two functions: watch and dispatch . But that’s all you need to create an event bus for loosely coupled code like dataStore and dataStore component.

You can use Observer in your own code by importing it just like you do with DataStore, from Composi’s data-store folder:

import { Observer } from 'composi/data-store'

Then you can create a watcher to listen for an event. You can create namespaced events or any valid string value. You also need to provide a callback that will execute when the event gets dispatched. The callback automatically receives as its argument any data passed along with the event:

import { Observer } from 'composi/data-store' const observer = new Observer()

observer.watch('something-happened', (data) => {

// Respond to the dispatched event and data:

console.log('The "something-happened" event was dispatched.')

console.log(`The data is: ${data}`)

}

Now, if we can use the observer to dispatch the event with some data. The dispatch takes two arguments: the event and some optional data:

observer.dispatch('something-happened', 'This is the event data.') // Result:

// The "something-happened" event was dispatched.

// The data is: This is the event data.

The above example is overly simplistic. You could be using data that is much more complicated. Also watchers and dispatcher do not have to provide deal with any data at all. That’s optional. You could use an observer solely for dealing with managing events in you code.