1 | # Component-DataStore
|
2 |
|
3 | This contains three classes: DataStore, DataStoreComponent and Observer. These enable creating Composi class components using a `dataStore` for state management. When the `dataStore` is modified, it automatically updates the component.
|
4 |
|
5 |
|
6 | ## Installation
|
7 |
|
8 | To install, run the following:
|
9 |
|
10 | ```
|
11 | npm i --save composi-datastore
|
12 | ```
|
13 |
|
14 | Then import DataStore and DataStoreComponent into your app:
|
15 |
|
16 | ```javascript
|
17 | import { h, Component } from 'composi'
|
18 | import { DataStore, DataStoreComponent } from 'composi-datastore'
|
19 | ```
|
20 |
|
21 | ## Create a `dataStore`
|
22 |
|
23 | With these imported, you can now create a `dataStore`. You do this by passing an object with a state property which defines the data you want the `dataStore` to manage.
|
24 |
|
25 | ```javascript
|
26 | const dataStore = new DataStore({
|
27 | state: {
|
28 | message: 'This is a simple dataStore',
|
29 | items: [
|
30 | {
|
31 | id: 101,
|
32 | value: 'Apples'
|
33 | },
|
34 | {
|
35 | id: 102,
|
36 | value: 'Oranges'
|
37 | }
|
38 | ]
|
39 | }
|
40 | })
|
41 | ```
|
42 |
|
43 | ## Define an `actions` Object
|
44 |
|
45 | Next we can create an `actions` object that our component will use. Actions will be methods that operate on the `dataStore`. Notice that we are passing the `dataStore` as the first argument of each method. This is so we can use its `setState` method to update the data and trigger a re-render of the component. The second arguemnt is any data we need for whatever data manipulation we need to do:
|
46 |
|
47 | ```javascript
|
48 | const actions = {
|
49 | addItem(dataStore, data) {
|
50 | dataStore.setState(prevState => {
|
51 | prevState.items.push({
|
52 | id: data.id,
|
53 | value: data.value
|
54 | })
|
55 | return prevState
|
56 | })
|
57 | },
|
58 | deleteItem(dataStore, id) {
|
59 | dataStore.setState(prevState => {
|
60 | prevState.items = prevState.items.filter(item => item.id != id)
|
61 | return prevState
|
62 | })
|
63 | }
|
64 | }
|
65 | ```
|
66 |
|
67 | ## Extend DataStoreComopnent Class
|
68 |
|
69 | And finally we need to create a component using the `DataStoreComponent` class. We do this just like we would with `Component`, by extending it. `DataStoreComponent` is an extension of `Component`, so the same lifecyle hooks and `render` function are available. Notice how we use the `dataStore` in the two methods that update state.
|
70 |
|
71 | ```javascript
|
72 | class List extends DataStoreComponent {
|
73 | key = 103
|
74 | render(data) {
|
75 | return (
|
76 | <div class='list-container'>
|
77 | <h2>{data.message}</h2>
|
78 | <p>
|
79 | <input type="text"/>
|
80 | <button className="add-item" onclick={() => this.addItem()}>Add</button>
|
81 | </p>
|
82 | <ul>
|
83 | {
|
84 | data.items.map(item => (
|
85 | <li key={item.id}>
|
86 | <span>{item.value}</span>
|
87 | <button className="delete-item" onclick={() => this.deleteItem(item.id)}>X</button>
|
88 | </li>)
|
89 | )
|
90 | }
|
91 | </ul>
|
92 | </div>
|
93 | )
|
94 | }
|
95 | componentDidMount() {
|
96 | this.input = this.element.querySelector('input')
|
97 | this.input.focus()
|
98 | }
|
99 | addItem() {
|
100 | const value = this.input.value
|
101 | if (value) {
|
102 | // Use the actions method to udpate the dataStore:
|
103 | actions.addItem(this.dataStore, {id: this.key++, value})
|
104 | this.input.value = ''
|
105 | this.input.focus()
|
106 | } else {
|
107 | alert('Please provide a value before submitting.')
|
108 | }
|
109 | }
|
110 | deleteItem(id) {
|
111 | // Use the actions method to udpate the dataStore:
|
112 | actions.deleteItem(this.dataStore, id)
|
113 | }
|
114 | }
|
115 | ```
|
116 |
|
117 | ## Instantiate the Component
|
118 |
|
119 | Now that we have a custom component derived from `DataStoreComponent`, we can create an instance. Like a normal Component instance, we pass it an object literal with several properties. The first is the container to render the component in. The second is the `dataStore` to use.
|
120 |
|
121 | After create the instance with the `new` keyword, we need to invoke the `update` method on it, passing in the `dataStore` state. This is because, unlike stateful components, components derived from `DataStoreComponent` don't have state. Their state is being handle by the `dataStore`. So, to trigger the first render, you need to pass the `dataStore` to the component through the `update` method. After that, any updates to the `dataStore` will trigger a re-render of the component.
|
122 |
|
123 | ```javascript
|
124 | const list = new List({
|
125 | container: 'section',
|
126 | dataStore
|
127 | })
|
128 |
|
129 | list.update(dataStore.state)
|
130 | ```
|
131 |
|
132 | ### Default DataStore Event
|
133 | `DataStore` uses `dataStoreStateChanged` as the default event in conjunction with `DataStoreComponent`. So DO NOT USE this for your own watchers. It will render your `DataStoreComponent` incapable of update when the dataStore changes.
|
134 |
|
135 | ## Observer
|
136 |
|
137 | This is a simple observer class with just two methods: `watch` and `dispatch`. Can't get any simpler than that. To use it, import it into your project:
|
138 |
|
139 | ```javascript
|
140 | import { Observer } from 'composi-datastore'
|
141 | ```
|
142 |
|
143 | After that you can create a new instance of `Observer`:
|
144 |
|
145 | ```javascript
|
146 | const observer = new Observer()
|
147 | ```
|
148 |
|
149 | After creating a new observer, you can set up watchers and when appropriate dispatch to them. You tell the observer to watch events and give them a callback to execute when the event happens. Dispatch announces the event and optionally passes some data along with it. Note, you don't need to send data. An watcher can just be waiting for an event to happen.
|
150 |
|
151 | ```javascript
|
152 | import { Observer } from 'composi-datastore'
|
153 |
|
154 | const observer = new Observer()
|
155 |
|
156 | function callback(data) {
|
157 | console.log(`This is the event: ${data}`)
|
158 | }
|
159 |
|
160 | observer.watch('test', callback)
|
161 |
|
162 | // Sometime later:
|
163 | observer.dispatch('test', 'Sending a message to the observer.')
|
164 | ```
|
165 |
|
166 | ### Events
|
167 |
|
168 | The observer stores its events on its `events` property. If we took the `observer` from the above example, we could examine its events like this:
|
169 |
|
170 | ```javascript
|
171 | console.log(oberser.events)
|
172 | /**
|
173 | returns: {test: [
|
174 | function (t){console.log("This is the event: "+t)}
|
175 | ]}
|
176 | */
|
177 | ```
|
178 |
|
179 | ### TODO
|
180 |
|
181 | Implement Jest tests for `DataStoreComponent`. Since this has a dependency on a Node module (composi), I haven't been able to create a test that works. Seems Jest has problems dealing with ES6 `import/export` with Babel. Have tried all kinds of configs, but Jest fails to import Composi module. |
\ | No newline at end of file |