UNPKG

14.5 kBMarkdownView Raw
1Composi
2=======
3
4Contents
5--------
6- [Components](./components.md)
7- [JSX](./jsx.md)
8- [Hyperx](./hyperx.md)
9- [Hyperscript](./hyperscript.md)
10- [Mount and Render](./render.md)
11- State
12- [Lifecycle Methods](./lifecycle.md)
13- [Events](./events.md)
14- [Styles](./styles.md)
15- [Unmount](./unmount.md)
16- [Installation](../README.md)
17- [Third Party Libraries](./third-party.md)
18- [Functional Components](./functional-components.md)
19- [Deployment](./deployment.md)
20
21State
22-----
23
24Components can be stateless or stateful. There are advantages to both. Chose the right one depending on use case. For example, if a component will only display the result of some other action, make it stateless and pass it the data it needs. If the data of a component can change at unpredictable times, make it stateful. That way, when its data changes, it will update automatically. If you have a fixed dataset and the component will never need to be rendered again during the session, make it stateless and pass it the data.
25
26When you give a component state, Composi assigns it getters and setters. Setting state causes the component to render a new virtual dom, diff it with the old virtual dom, and if there are differences, update the actual DOM. As such, the assignment of data to a component's state will trigger an update. Setting state directly is ideal for primitive data types, such as string, numbers:
27
28```javascript
29const helloWorld = new Component({
30 root: '#hello',
31 state: 'World',
32 render: (message) => (
33 <h1>Hello, {message}!</h1>
34 )
35})
36// Inject component in DOM:
37helloWorld.update()
38```
39
40With the above component, we can change its output by directly accessing the component's state:
41
42```javascript
43helloWorld.state = 'everybody'
44```
45
46Booleans
47---------
48By default boolean values are not output. This is so they can be used for conditional checks. However, if you want to output a boolean value, just convert it to a string. There are two ways to do this. For `true` or `false` you can add `toString()` to convert them to strings. The values `null` and `undefined` do not have a `toString()` function, but you can use string concatenation to convert them, or use `String(value)`. Below are examples of these approaches:
49
50```javascript
51// For true or false:
52render: (value) => <p>The boolean value is: {value.toString()}</p>
53
54// The above approach would throw an error if the boolean was undefined or null.
55// For them, do the following:
56render: (value) => <p>The boolean value is: {value + ''}</p>
57
58// Make boolean uppercase:
59render: (value) => <p>The boolean value is: {(String(value).toUpperCase()}</p>
60```
61
62Complex Data Types
63------------------
64Most components will have state of complex data types: objects or arrays. To update the state of complex types you have two choices: use the `setState` method or get the state, perform your operations and set the state with the final result. We'll look at `setState` first.
65
66setState
67--------
68The Component class has a method called `setState`. Actually, you can use `setState` to set the state of primitive types. So, with the helloWorld component above, we could update its state like this:
69
70```javascript
71helloWorld.state = 'everybody'
72
73// or:
74helloWorld.setState('everybody')
75```
76
77But `setState` is really about making it easy to update the state of complex types. Lets look at a component with an object for its state:
78
79```javascript
80const personComponent = new Component({
81 root: '#personDiv',
82 state: {
83 firstName: 'Joe',
84 lastName: 'Bodoni',
85 job: 'Mechanic',
86 age: 23
87 },
88 render: (person) => (
89 <div>
90 <p>Name: {}</p>
91 <p>Job{}</p>
92 </div>
93 )
94})
95```
96
97Because this component's state is complex, we cannot directly update the state properties:
98
99```javascript
100// This assignment will not work:
101personComponent.state.job = 'Web Developer'
102```
103
104Instead we need to use the `setState` method. To update a state object property, we pass in an object with that property and the new value. Behind the scenes, `setState` will mixin the new object with the state object. Then it will create a new virtual dom and patch the actual DOM to reflect the changes.
105
106```javascript
107// Update the job property of the component's state:
108personComponent.setState({job: 'Web Developer'})
109```
110
111Updating Array State
112--------------------
113
114When a component has an array as state, you need to provide a second argument for `setState`: the index of the array where you want to make the index. For instance, suppose we have a component `fruitList` that prints out a list of fruits. We notice that the third item in the list is mispelled and want to update it. We can do that as follows:
115
116```javascript
117fruitList.state = ['Apples', 'Oranges', 'Pinapplez', 'Bananas']
118
119// Use second argument for index in the array you want to update:
120fruitList.setState('Pinapples', 2)</code>
121```
122
123### Arrays of Objects
124Arrays of objects are more complicated. This is because when you use `setState` on an array, it is actually performing a splice operation on the array. That means that if you only pass in an object with the property you want to update, as we did for objects, the entire object at that position in the array will be replaced by what you provided. Therefore to update an object in an array you need to provide a complete object for the update, or update the object first and then pass it in. Let's suppose we have an array of people objects:
125
126```javascript
127const people = [
128 {
129 firstName: 'Joe',
130 lastName: 'Bodoni',
131 job: 'Mechanic'
132 },
133 {
134 firstName: 'Ellen',
135 lastName: 'Vanderbilt',
136 job: 'Lab Technician'
137 },
138 {
139 firstName: 'Sam',
140 lastName: 'Anderson',
141 job: 'Web Developer'
142 }
143]
144```
145This array is used as the state for a component of users. We want to update Joe's job to Rock Star. You might try to do this:
146
147```javascript
148// Update job for Joe Bodoni:
149userList.setState({job: 'Rock Star'}, 0)
150```
151
152This will instead result in the object loosing the firstName and lastName properties, and the list in the DOM would be updated accordingly. Instead, we need to get the object we want to update from the array, make whatever changes we need to and then set it on the state:
153
154```javascript
155// Proper way to update an object in an array.
156// Get the Joe Bodoni user from state:
157const state = userList.state[0]
158
159// Set job to new value:
160state.job = 'Rock Star'
161
162// Update component's state:
163userList.state = state
164```
165
166Complex State Operations
167------------------------
168
169As we saw in our last example of arrays, sometimes you will need to get the state, operate on it separately and then set the component's state to that. For example, if you need to use map, filter, sort or reverse on an array, you'll want to get the complete state and perform these operations. Aftwards you can just set the state:
170
171```javascript
172// Get the component's state:
173const state = fruitsList.state
174
175// Reverse the array:
176state.reverse()
177
178// Set the component's state with the new state:
179fruitsList.state = state
180```
181
182setState with a Callback
183------------------------
184One option for handling the need for complex operations when setting state is to pass a callback to the `setState` method. When you do so, the first argument of the callback will be the component's state. In the example below, notice how we get the state and manipulate it in the `handleClick` method. After doing what you need to with state, remember to return it. Otherwise the component's state will not get updated.
185
186```javascript
187class Button extends Component {
188 constructor(props) {
189 super(props)
190 this.state = { counter: 1 }
191 }
192 render() {
193 return <button onclick={() => this.handleClick()}>{this.state.counter}</button>
194 }
195 handleClick() {
196 // Use callback in setState to manilate state before retuning it:
197 this.setState(state => {
198 if (state.counter < 10) {
199 return {counter: state.counter + 1}
200 }
201 })
202 }
203}
204
205// Create instance of button:
206// Clicking on the new button will encrease its value upto 10.
207const button = new Button()
208```
209
210Be aware that whatever the callback returns will be set as the component's new state. Therefore you will need to make whatever changes to the component's whole state before returning it.
211
212Keyed Items
213-----------
214
215In general, Composi's diffing algorythm is very efficient at determining what changed. However, when dealing with long lists, especially if the items order has changed in a random way, it can be challenging to determine the best way to patch the DOM. Keys provide a mechnamism to remedy this. If you are not going to make any drastic changes to a list's data, keys are not necessary. But if the order of updated items can change dramatically, you'll want to use keyed data.
216
217### Keyed Array Data
218The easiest way to create a keyed list is to have any array of data with unique ids. These ids only need to be unique for the items in the array the list is using. You can use any scheme to create unique ids for array items. You could use a `uuid` module from [NPM](https://www.npmjs.com/search?q=uuid).
219
220Below is an example of some keyed data:
221
222```javascript
223const fruits = [
224 {
225 id: 'a101',
226 name: 'Apple',
227 price: '1.00'
228 },
229 {
230 id: 'a102',
231 name: 'Orange',
232 price: '1.50'
233 },
234 {
235 id: 'a103',
236 name: 'Banana',
237 price: '2.00'
238 }
239]
240```
241
242To create a keyed list we need to provide each item in the list a `key` property. Notice how the following template does that:
243
244```javascript
245const fruitList = new Component({
246 root: '#fruits',
247 state: fruits,
248 render: (fruits) => (
249 <ul>
250 {
251 fruits.map(fruit => <li key={fruit.id}>{fruit.name}</li>)
252 }
253 </ul>
254 )
255})
256```
257
258The diffing algorythm will use the key value to understand the order of items in the list. This results in more efficient diffing and patching. Putting the `key` properting in the markup of a list will not be rendered to the DOM. The `key` property will only exist in the virtual dom, where it is used to determine if the order of list elements has changed.
259
260Keys Must Be Unique
261-------------------
262When using keys, make sure that each key is unique to that dataset. Otherwise, if they are not the diff and patch algorythms will get confused and produced unpredictable results when patching the DOM. You can use whatever means you want to create keys as long as they are unique.
263
264State in Extended Components
265----------------------------
266
267When you extend Component to create a specialized class, you may want to set initial state for all instances of the class. You can set state directly in the constructor using the `this` keyword:
268
269```javascript
270class Clock extends Component {
271 constructor(opts) {
272 super(opts)
273 // Set state for all class instances:
274 this.state = {time: Date.now()
275 }
276 render() {
277 let time = this.state.time
278 const angle = (time) => 2 * Math.PI * time / 60000
279 return (
280 <li>
281 <div>
282 <svg viewBox="0 0 100 100" width="150px">
283 <circle cx="50" cy="50" r="45" fill="blue" />
284 <line
285 stroke-width="2"
286 x1="50"
287 y1="50"
288 x2={ 50 + 40 * Math.cos(angle(time)) }
289 y2={ 50 + 40 * Math.sin(angle(time)) }
290 stroke="white"
291 />
292 </svg>
293 </div>
294 </li>
295 )
296 }
297}
298```
299
300
301Third Party State Management
302----------------------------
303Because you can create stateless components, you can use thrid party state management solutions with Composi. Redux, Mobx, or roll your own.
304
305Redux
306-----
307
308```javascript
309const { h, Component } from 'composi'
310const { createStore } from 'redux'
311
312// Reducer:
313function count(state=0, action) {
314 switch(action.type) {
315 case 'INCREMENT':
316 if (state > 99) return 100
317 return state + 1
318 case 'DECREMENT':
319 if (state < 1) return 0
320 return state - 1;
321 default:
322 return state
323 }
324}
325
326// Action Creators:
327const increment = () => {
328 return {
329 type: 'INCREMENT'
330 };
331};
332
333const decrement = () => {
334 return {
335 type: 'DECREMENT'
336 };
337};
338
339// Create Redux store:
340const store = createStore(count)
341
342
343// Extend Component to create counter:
344class Counter extends Component {
345 constructor(opts) {
346 super(opts)
347
348 // Assigning store to component:
349 this.store = opts.store
350
351 // Update component when store state changes:
352 store.subscribe(() => this.updateFromStore())
353
354 // Give counter default value of "0":
355 this.render = (count = 0) => (
356 <div id="counter">
357 <button id="dec" disabled={count==0} onclick={() => this.dec()}>-</button>
358 <span id="text">{count}</span>
359 <button id="inc" onclick={() => this.inc()}>+</button>
360 </div>
361 )
362 }
363
364 // Use ths method to udpate counter with state changes:
365 updateFromStore() {
366 const state = this.store.getState()
367 this.update(state)
368 }
369
370 inc() {
371 this.store.dispatch(increment())
372 }
373
374 dec() {
375 this.store.dispatch(decrement())
376 }
377}
378
379// Create new counter:
380const counter = new Counter({
381 root: 'article',
382 store: store
383})
384counter.update()
385```
386
387This was a trivial example of using Redux with Composi. Please consult [Redux documention](http://redux.js.org/docs/basics/) to learn more about how Redux can solve your state management needs.
388
389Mobx
390----
391
392You can also use [Mobx](https://mobx.js.org) for state management. Like in the Redux example above, you'll want to use state components.
393
394```javascript
395const {h, Component} from 'composi'
396const { observable, autorun } from 'mobx'
397
398const store = observable({ count: 0})
399
400// Extend Component to create counter:
401class Counter extends Component {
402 constructor(opts) {
403 super(opts)
404 this.root = 'article'
405
406 // Assign Mobx obersable store to Counter:
407 this.store = store
408
409 // Give counter default value of "0":
410 this.render = (count = 0) => (
411 <div id="counter">
412 <button id="dec" onclick={() => this.dec()} disabled={count==0}>-</button>
413 <span id="text">{count}</span>
414 <button id="inc" onclick={() => this.inc()} disabled={count>19}>+</button>
415 </div>
416 )
417 }
418
419 inc() {
420 this.store.count++
421 }
422
423 dec() {
424 this.store.count--
425 }
426}
427
428// Create new counter:
429const counter = new Counter()
430
431// Capture observable store changes and update counter:
432autorun(() => counter.update(counter.store.count))
433```
434
435Of course, Mobx has many more useful and powerful features. This was just a trivial example. Consult the [Mobx documentation](https://mobx.js.org/refguide/api.html) to learn more about how Mobx can solve your state management needs.