1 | Composi
|
2 | =======
|
3 |
|
4 | Contents
|
5 | --------
|
6 | - [Installation](../README.md)
|
7 | - [JSX](./jsx.md)
|
8 | - [Hyperx](./hyperx.md)
|
9 | - [Hyperscript](./hyperscript.md)
|
10 | - [Functional Components](./functional-components.md)
|
11 | - [Mount, Render and Unmount](./render.md)
|
12 | - [Components](./components.md)
|
13 | - [State](./state.md)
|
14 | - [Lifecycle Methods](./lifecycle.md)
|
15 | - [Events](./events.md)
|
16 | - [Styles](./styles.md)
|
17 | - [Unmount](./unmount.md)
|
18 | - State Management with DataStore
|
19 | - [State Management with DataStore](#State-Management-with-DataStore)
|
20 | - [Importing in Your Project](#Importing-in-Your-Project)
|
21 | - [DataStore](#DataStore)
|
22 | - [DataStoreComponent](#DataStoreComponent)
|
23 | - [Observer](#Observer)
|
24 | - [Watch Method](#Watch-Method)
|
25 | - [Dispatch Method](#Dispatch-Method)
|
26 | - [uuid](#uuid)
|
27 | - [Third Party Libraries](./third-party.md)
|
28 | - [Deployment](./deployment.md)
|
29 | - [Differences with React](./composi-react.md)
|
30 |
|
31 | ## State Management with DataStore
|
32 |
|
33 | 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.
|
34 |
|
35 | 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.
|
36 |
|
37 | If you are going to use `DataStore` then you are also going to use `DataStoreComponent`.
|
38 |
|
39 | ## Importing in Your Project
|
40 |
|
41 | Importing `DataStore` into your project is simple. You import it from Composi's `data-store` folder:
|
42 |
|
43 | ```javascript
|
44 | import { DataStore, DataStoreComponent } from 'composi/data-store'
|
45 | ```
|
46 |
|
47 | ## DataStore
|
48 |
|
49 | 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:
|
50 |
|
51 | ```javascript
|
52 | import { DataStore, DataStoreComponent } from 'composi/data-store'
|
53 |
|
54 | const dataStore = new DataStore({
|
55 | state: {
|
56 | title: 'The Current Title',
|
57 | items: [
|
58 | 'Apples',
|
59 | 'Oranges',
|
60 | 'Bananas'
|
61 | ]
|
62 | }
|
63 | })
|
64 | ```
|
65 |
|
66 | ### setState
|
67 |
|
68 | `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:
|
69 |
|
70 | ```javascript
|
71 | dataStore.setState(prevState => {
|
72 | prevState.items.push('Watermelon')
|
73 | // Don't forget to return prevState:
|
74 | return prevState
|
75 | })
|
76 | ```
|
77 |
|
78 | ## DataStoreComponent
|
79 |
|
80 | 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.
|
81 |
|
82 | To create a new `DataStoreComponent` you need to extend it, just like you would with the `Component` class:
|
83 |
|
84 | ```javascript
|
85 | import { h } from 'composi'
|
86 | import { DataStore, DataStoreComponent } from 'composi/data-store'
|
87 |
|
88 | // Define a dataStore:
|
89 | const dataStore = new DataStore({
|
90 | state: {
|
91 | title: 'The Current Title',
|
92 | items: [
|
93 | 'Apples',
|
94 | 'Oranges',
|
95 | 'Bananas'
|
96 | ]
|
97 | }
|
98 | })
|
99 |
|
100 | // Define a custom component:
|
101 | class List extends DataStoreComponent {
|
102 | render(data) {
|
103 | return (
|
104 | <ul class='list'>
|
105 | {
|
106 | data.items.map(item => <li>{item}</li>)
|
107 | }
|
108 | </ul>
|
109 | )
|
110 | }
|
111 | }
|
112 | ```
|
113 |
|
114 | 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`:
|
115 |
|
116 | ```javascript
|
117 | const list = new List(
|
118 | container: 'section',
|
119 | dataStore
|
120 | )
|
121 | ```
|
122 |
|
123 | 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.
|
124 |
|
125 | 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.
|
126 |
|
127 |
|
128 | ## Example
|
129 |
|
130 | 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.
|
131 |
|
132 | ```javascript
|
133 | import { h } from 'composi'
|
134 | import { DataStore, DataStoreComponent } from 'composi/data-store'
|
135 |
|
136 | // Define the dataStore:
|
137 | const dataStore = new DataStore({
|
138 | state: {
|
139 | message: 'Bozo the clown',
|
140 | items: [
|
141 | {
|
142 | id: 101,
|
143 | value: 'Apples'
|
144 | },
|
145 | {
|
146 | id: 102,
|
147 | value: 'Oranges'
|
148 | }
|
149 | ]
|
150 | }
|
151 | })
|
152 |
|
153 | // Define actions to manipulate the dataStore:
|
154 | const actions = {
|
155 | addItem(dataStore, data) {
|
156 | dataStore.setState(prevState => {
|
157 | prevState.items.push({
|
158 | id: data.id,
|
159 | value: data.value
|
160 | })
|
161 | return prevState
|
162 | })
|
163 | },
|
164 | deleteItem(dataStore, id) {
|
165 | dataStore.setState(prevState => {
|
166 | prevState.items = prevState.items.filter(item => item.id != id)
|
167 | return prevState
|
168 | })
|
169 | }
|
170 | }
|
171 |
|
172 | // Create a custom component by extending DataStoreComponent:
|
173 | class List extends DataStoreComponent {
|
174 | key = 103
|
175 | render(data) {
|
176 | return (
|
177 | <div class='list-container'>
|
178 | <h2>{data.message}</h2>
|
179 | <p>
|
180 | <input type="text"/>
|
181 | <button className="add-item" onclick={() => this.addItem()}>Add</button>
|
182 | </p>
|
183 | <ul>
|
184 | {
|
185 | data.items.map(item => (
|
186 | <li key={item.id}>
|
187 | <span>{item.value}</span>
|
188 | <button className="delete-item" onclick={() => this.deleteItem(item.id)}>X</button>
|
189 | </li>)
|
190 | )
|
191 | }
|
192 | </ul>
|
193 | </div>
|
194 | )
|
195 | }
|
196 | componentDidMount() {
|
197 | this.input = this.element.querySelector('input')
|
198 | this.input.focus()
|
199 | }
|
200 | addItem() {
|
201 | const value = this.input.value
|
202 | if (value) {
|
203 | actions.addItem(this.dataStore, {id: this.key++, value})
|
204 | this.input.value = ''
|
205 | this.input.focus()
|
206 | } else {
|
207 | alert('Please provide a value before submitting.')
|
208 | }
|
209 | }
|
210 | deleteItem(id) {
|
211 | actions.deleteItem(this.dataStore, id)
|
212 | }
|
213 | }
|
214 |
|
215 | // Create an instance of the custom component.
|
216 | // Assign it a container and pass in the dataStore:
|
217 | const list = new List({
|
218 | container: 'section',
|
219 | dataStore
|
220 | })
|
221 |
|
222 | // Don't forget to pass the dataStore to the list instance through the update method.
|
223 | // You need to do this to forceß the component to mount the first time..
|
224 | list.update(dataStore.state)
|
225 | ```
|
226 |
|
227 | ## Observer
|
228 |
|
229 | `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:
|
230 |
|
231 | ```javascript
|
232 | import { Observer } from 'composi/data-store'
|
233 | ```
|
234 |
|
235 | To you `Observer` you need to create a new instance first:
|
236 |
|
237 | ```javascript
|
238 | import { Observer } from 'composi/data-store'
|
239 |
|
240 | const observer = new Observer()
|
241 | ```
|
242 |
|
243 | ### Watch Method
|
244 |
|
245 | 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:
|
246 |
|
247 | ```javascript
|
248 | // Setting up a simple data.
|
249 | // We're not really doing anything with the data.
|
250 | // Just outputting it to show that it arrived.
|
251 | observer.watch('boring', function(data) {
|
252 | console.log(`The boring event fired. Here's the data:`)
|
253 | console.log(data)
|
254 | })
|
255 | ```
|
256 |
|
257 | ### Dispatch Method
|
258 |
|
259 | Then we can dispatch our event with some data:
|
260 |
|
261 | ```javascript
|
262 | observer.dispatch('boring', 'This is the new data.')
|
263 | ```
|
264 |
|
265 | You can pass whatever kind of data you need to: boolean, number, string, array or object.
|
266 |
|
267 | ## uuid
|
268 |
|
269 | 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.
|
270 |
|
271 | ```javascript
|
272 | import { uuid } from 'composi/data-store
|
273 |
|
274 | const id = uuid()
|
275 | ```
|