Composi DataStore

Composi has some extra classes that let you create stateless class component with a dataStore for state management. This results in something similar to how Mobx works with React components. When you update the dataStore, the component updates automatically. This is accomplished through observers used by both the dataStore and component.

Composi DataStore provides two classes: dataStore and dataStore component. To use these, you first need to import them into your project. They are located in a folder called data-store, so you import them like this:

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

Creating a dataStore

After importing the `DataStore` class, you can create a new instance. On doing so you need to pass in the data you want it to use. The data needs to be an object literal with a state attribute. To that you assign your data, using an attribute that makes sense for your data. You'll use that attribute to access the data when using the dataStore.

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

const dataStore = new DataStore({
  state: {
    employees: [
      {
        name: 'Joe Bodoni',
        position: 'mechanic'
      }, 
      { 
        name: 'Suzan Maxwell', 
        position: 'accountant' 
      },
      { 
        name: 'Stan Harding', 
        position: 'manager' 
      }
    ]
  }
})

Using setState on a dataStore

A dataStore exposes one method to update its data: setState. This takes a callback, which gets passed the dataStore's data as its argument. Usually this is expressed as prevState. After manipulating it, you need to return it. If you forget to return prevState, the dataStore's state will never be updated.

Let's take the dataStore we created earlier and update it by adding a new employee:

dataStore.setState(prevState => {
  prevState.employees.push({
    name: 'Rebecca Sawyers',
    position: 'legal counsel'
  })
  // Don't forget to return prevState:
  return prevState
}

When you update a dataStore using setState, the dataStore fires an event called dataStoreStateChanged and it passes the updated state with that event. If you have a dataStore component linked to that dataStore, it would receive that event and data and re-render itself. Read the next part about create a dataStore component

Creating a DataStoreComponent

A dataStore by itself is not very useful for your app, but in combination with dataStore component it creates a reactive component.

To create a dataStore component we first need to import it from the Composi data-store folder:

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

You create a dataStore component the same way you would create a Component class component: define a render function, maybe add some lifecycle hooks, etc. Whatever you can do with a class component you can do with a dataStore component.

DataStore with Component

Here's an example of a dataStore and a component using it. First we define a dataStore, then we define a component. And finally, when we instantiate the component with the new keyword, we pass in the dataStore as a property. During initialization the coponent uses that dataStore to setup a watcher for the dataStoreStateChanged event.

import { h } from 'composi' 
import { DataStore, DataStoreComponent } from 'composi/data-store' 
  
// Define the dataStore: const
dataStore = new DataStore({ state: 
  { 
    items: [ 
      { 
        id: 101, 
        value: 'Apples' 
      }, 
      { 
        id: 102, 
        value: 'Oranges' 
      } 
    ] 
  }
})

// Define dataStore component:
class List extends DataStoreComponent {
  render(data) {
    return (
      <ul>
        {
          data.items.map(item => <li key={item.id}>{item.value}</li>)
        }
      </ul>
    )
  }
}

// Create instance of List.
// Give it a container and pass in dataStore.
const list = new List({
  container: document.body,
  dataStore
})

// Force first render by calling update on component.
// Pass in dataStore.state as argument.
list.update(dataStore.state)

With the above code, if we modify the dataStore using its setState method, the list will automatically update:

dataStore.setState(prevState => {
  prevState.items.push({
    id: 103,
    value: 'Bananas'
  })
  // Don't forget to retur prevState!
  return prevState
})

Doing the above transform on the dataStore would cause the list to re-render with the new data.

This combination of dataStore with dataStore component enables reactive components with separate dataStore for state management.

Example of DataStore Component

Here's a working example of a dataStore and dataStore component. It also has a simple actions object that the component uses to update the dataStore's state:

See the Pen DataStore and DataStoreComponent by Robert Biggs (@rbiggs) on CodePen.

Using the Observer Class

Both DataStore and DataStoreComponent use the Observer class. If you want, you can also use this to create event watchers and dispatch data. This enables setting up a simple event bus for decoupled reactivity in your code.

Observer has two methods: watch and dispatch. watch takes two arguments: an event and a callback to execute whent he event occurs. The callback receives as its argument any data passed along with the event. dispatch takes two arguments: the event to dispatch and any data you want to pass along. Of course, in both cases, you do not have to have an event that uses data. You could create an observer that just reacts to an event being dispatched.

To create an observer, you need to import it from the Composi data-store folder:

import { Observer } from 'composi/data-store

With the Observer class imported, you can create a new instance:

import { Observer } from 'composi/data-store

const observer = new Observer()

Setting Up a Watcher

After creating an instance of Observer, you can set up a watcher. You do this by providing an event to watch and a callback to execute when the event occurs. The callbacks argument will be any data that was dispatched with the event.

import { Observer } from 'composi/data-store

const observer = new Observer()
observer.watch('something-happened', data => {
  console.log('something-happened event occured.')
  console.log(`Recevied the following data: ${data}`)
})

With a watcher defined, we are ready to dispatch an event:

Dispatch an Event with Data

When you have an observer watcher setup, you can make it react by dispatching its event along with some data. You do that using the observer's dispatch method. That takes two arguments: the event you want to trigger and optionally some data to pass to the watcher's callback.

import { Observer } from 'composi/data-store

const observer = new Observer()
observer.watch('something-happened', data => {
  console.log('something-happened event occured.')
  console.log(`Recevied the following data: ${data}`)
})

// Sometime later:
observer.dispatch('something-happened', 'This is some data being passed along.')

// result:
// something-happened event occured.
// Recevied the following data: This is some data being passed along.

The above observer was dealing with a simple string as data. You can use whatever kind of data you need to: boolean, string, number, array or object.