Skip to content
This repository has been archived by the owner on Sep 3, 2022. It is now read-only.

Latest commit

 

History

History
275 lines (224 loc) · 8.4 KB

data-store.md

File metadata and controls

275 lines (224 loc) · 8.4 KB

Composi

Contents

State Management with DataStore

Composi's class components are a powerful way to structure your apps. Stateful class components let you create components with local state. In many situations this is a perfect way to handle state. But you may prefer to have state separate from your class components.

For those situations Composi provides DataStore and DataStoreComponent. Together they create a combination similar to how Mobx works with React components. You create a dataStore and pass it to a dataStore component. When you update the data in the dataStore, the dataStore component udpates automatically.

If you are going to use DataStore then you are also going to use DataStoreComponent.

Importing in Your Project

Importing DataStore into your project is simple. You import it from Composi's data-store folder:

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

DataStore

After importing DataStore, you can create a dataStore. You do this using the new keyword and passing in the intial data to use. DataStore expects data in the following format:

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

const dataStore = new DataStore({
  state: {
    title: 'The Current Title',
    items: [
      'Apples',
      'Oranges',
      'Bananas'
    ]
  }
})

setState

DataStore has only one public method: setState. You use this to update the state of a dataStore. You do this by using a callback. This gets pass the previous state of the dataStore. After doing whatever you need to do with prevState, you need to return it. Otherwise, the dataStore with never dispatch its update event:

dataStore.setState(prevState => {
  prevState.items.push('Watermelon')
  // Don't forget to return prevState:
  return prevState
})

DataStoreComponent

To make your dataStore useful, you need to pass it to an instance of DataStoreComponent. This is just a custom version of Composi's Component class witout state. It's configured to use a dataStore instead of state. It watches the dataStore. When you update the dataStore, it dispatches and event that DataStoreComponent listens for. When that happens, the component updates with the data that was sent.

To create a new DataStoreComponent you need to extend it, just like you would with the Component class:

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

// Define a dataStore:
const dataStore = new DataStore({
  state: {
    title: 'The Current Title',
    items: [
      'Apples',
      'Oranges',
      'Bananas'
    ]
  }
})

// Define a custom component:
class List extends DataStoreComponent {
  render(data) {
    return (
      <ul class='list'>
        {
          data.items.map(item => <li>{item}</li>)
        }
      </ul>
    )
  }
}

With the custom component defined, we can instatiate it. When doing so, we need to give provide container to render in and pass in the dataStore:

const list = new List(
  container: 'section',
  dataStore
)

And that's it. The component is now linked to the dataStore. If we change the dataStore's state, the component will udpate automatically. We would do that using setState on the dataStore, as we did above.

Although the component has no local state, the component will mount as soon as it's instantiated. This is new in version 3.2.0. In earlier versions you had to mount the component using the update method.

Example

Here's a complete example using DataStore and DataStoreComponent. In this example we separate out the code that updates the dataStore into an actions object. The component's user interactions will invoke those actions methods, which will result in the component itself being updated.

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

// Define the dataStore:
const dataStore = new DataStore({
  state: {
    message: 'Bozo the clown',
    items: [
      {
        id: 101,
        value: 'Apples'
      },
      {
        id: 102,
        value: 'Oranges'
      }
    ]
  }
})

// Define actions to manipulate the dataStore:
const actions = {
  addItem(dataStore, data) {
    dataStore.setState(prevState => {
      prevState.items.push({
        id: data.id,
        value: data.value
      })
      return prevState
    })
  },
  deleteItem(dataStore, id) {
    dataStore.setState(prevState => {
      prevState.items = prevState.items.filter(item => item.id != id)
      return prevState
    })
  }
}

// Create a custom component by extending DataStoreComponent:
class List extends DataStoreComponent {
  key = 103
  render(data) {
    return (
      <div class='list-container'>
        <h2>{data.message}</h2>
        <p>
          <input type="text"/>
          <button className="add-item" onclick={() => this.addItem()}>Add</button>
        </p>
        <ul>
          {
            data.items.map(item => (
              <li key={item.id}>
                <span>{item.value}</span>
                <button className="delete-item" onclick={() => this.deleteItem(item.id)}>X</button>
              </li>)
            )
          }
        </ul>
      </div> 
    )
  }
  componentDidMount() {
    this.input = this.element.querySelector('input')
    this.input.focus()
  }
  addItem() {
    const value = this.input.value
    if (value) {
      actions.addItem(this.dataStore, {id: this.key++, value})
      this.input.value = ''
      this.input.focus()
    } else {
      alert('Please provide a value before submitting.')
    }
  }
  deleteItem(id) {
    actions.deleteItem(this.dataStore, id)
  }
}

// Create an instance of the custom component.
// Assign it a container and pass in the dataStore:
const list = new List({
  container: 'section',
  dataStore
})

// Don't forget to pass the dataStore to the list instance through the update method.
// You need to do this to forceß the component to mount the first time..
list.update(dataStore.state)

Observer

DataStore exposes the Observer class that it uses internally so you can use it in your projects. Observer exposes two methods: watch and dispatch. You can use these to create an event bus to decouple your code. Like DataStore, you import Observer from Composi's data-store folder:

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

To you Observer you need to create a new instance first:

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

const observer = new Observer()

Watch Method

Then you can create a watcher to listen for an event. You also need to provide a callback to execute when the event is dispatched. The callback will get passed the data as its argument:

// Setting up a simple data.
// We're not really doing anything with the data.
// Just outputting it to show that it arrived.
observer.watch('boring', function(data) {
  console.log(`The boring event fired. Here's the data:`)
  console.log(data)
})

Dispatch Method

Then we can dispatch our event with some data:

observer.dispatch('boring', 'This is the new data.')

You can pass whatever kind of data you need to: boolean, number, string, array or object.

uuid

The DataStore class uses the uuid function to create an RFC4122 version 4 compliant uuid. This is a random string of 36 characters. This is used internally by DataStore. You can also import uuid into your project to create a uuid for your own code.

import { uuid } from 'composi/data-store

const id = uuid()