UNPKG

15.9 kBMarkdownView Raw
1Composi
2=======
3
4Contents
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
14 - [State](#State)
15 - [Booleans](#Booleans)
16 - [Complex Data Types](#Complex-Data-Types)
17 - [setState](#setState)
18 - [Updating Array State](#Updating-Array-State)
19 - [Arrays of Objects](#Arrays-of-Objects)
20 - [Complex State Operations](#Complex-State-Operations)
21 - [setState with a Callback](#setState-with-a-Callback)
22 - [Keyed Items](#Keyed-Items)
23 - [Keyed Array Data](#Keyed-Array-Data)
24 - [Keys Must Be Unique](#Keys-Must-Be-Unique)
25 - [Never Use Loop Index for Keys](#Never-Use-Loop-Index-for-Keys)
26 - [State in Class Components](#State-in-Class-Components)
27 - [Third Party State Management](#Third-Party-State-Management)
28 - [Redux](#Redux)
29 - [Mobx](#Mobx)
30- [Lifecycle Methods](./lifecycle.md)
31- [Events](./events.md)
32- [Styles](./styles.md)
33- [Unmount](./unmount.md)
34- [State Management with DataStore](./data-store.md)
35- [Third Party Libraries](./third-party.md)
36- [Deployment](./deployment.md)
37- [Differrences with React](./composi-react.md)
38
39## State
40
41Components 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.
42
43When 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:
44
45```javascript
46const helloWorld = new Component({
47 root: '#hello',
48 state: 'World',
49 render: (message) => (
50 <h1>Hello, {message}!</h1>
51 )
52})
53// Inject component in DOM:
54helloWorld.update()
55```
56
57With the above component, we can change its output by directly accessing the component's state:
58
59```javascript
60helloWorld.state = 'everybody'
61```
62
63## Booleans
64
65By 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:
66
67```javascript
68// For true or false:
69render: (value) => <p>The boolean value is: {value.toString()}</p>
70
71// The above approach would throw an error if the boolean was undefined or null.
72// For them, do the following:
73render: (value) => <p>The boolean value is: {value + ''}</p>
74
75// Make boolean uppercase:
76render: (value) => <p>The boolean value is: {(String(value).toUpperCase()}</p>
77```
78
79## Complex Data Types
80
81Most 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.
82
83## setState
84
85The 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:
86
87```javascript
88// Do not use this:
89helloWorld.state = 'everybody'
90
91// Instead do this:
92helloWorld.setState('everybody')
93```
94
95But `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:
96
97```javascript
98const personComponent = new Component({
99 root: '#personDiv',
100 state: {
101 firstName: 'Joe',
102 lastName: 'Bodoni',
103 job: 'Mechanic',
104 age: 23
105 },
106 render: (person) => (
107 <div>
108 <p>Name: {}</p>
109 <p>Job{}</p>
110 </div>
111 )
112})
113```
114
115Because this component's state is complex, we cannot directly update the state properties:
116
117```javascript
118// This assignment will not work:
119personComponent.state.job = 'Web Developer'
120```
121
122Instead 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.
123
124```javascript
125// Update the job property of the component's state:
126personComponent.setState({job: 'Web Developer'})
127```
128
129## Updating Array State
130
131When 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:
132
133```javascript
134const fruitList = new Component({
135 container: 'main',
136 state: ['Apples', 'Oranges', 'Pinapplez', 'Bananas'],
137 render: (fruits) => {
138 return (
139 <ul>
140 {
141 fruits.map(fruit => <li>{fruit}</li>)
142 }
143 </ul>
144 )
145 }
146})
147
148// Use second argument for index in the array you want to update:
149fruitList.setState(prevState => {
150 prevState[2] = 'Pinapples'
151 // Return new state to update component:
152 return prevState
153})</code>
154```
155
156## Arrays of Objects
157Arrays 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:
158
159```javascript
160const people = [
161 {
162 firstName: 'Joe',
163 lastName: 'Bodoni',
164 job: 'Mechanic'
165 },
166 {
167 firstName: 'Ellen',
168 lastName: 'Vanderbilt',
169 job: 'Lab Technician'
170 },
171 {
172 firstName: 'Sam',
173 lastName: 'Anderson',
174 job: 'Web Developer'
175 }
176]
177```
178This 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:
179
180```javascript
181// Update job for Joe Bodoni:
182userList.setState({job: 'Rock Star'}, 0)
183```
184
185The above operation will not update the user Joe Bodoni as you might think. It will result in the user's first and last names being deleted. Instead, we need to use a function to get the state and update the user's job at the correct index of the state object:
186
187```javascript
188// Proper way to update state. Use setState to access the index of array, make change and return it:
189userList.setState(prevState => {
190 // Update job of first person object in array:
191 prevState[0].job = 'Rock Star'
192 // Return changed state to update component:
193 return prevState
194})
195```
196
197## Complex State Operations
198
199As 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:
200
201```javascript
202// Use setState with a callback to get the state, reverse it and return it:
203fruitsList.setState(prevState => {
204 prevState.reverse()
205 return prevState
206})
207```
208
209Read the next section for more details on using a callback to set state.
210
211## setState with a Callback
212
213When you pass a callback to the `setState` method, 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.
214
215```javascript
216class Button extends Component {
217 constructor(props) {
218 super(props)
219 this.state = { counter: 1 }
220 }
221 render() {
222 return <button onclick={() => this.handleClick()}>{this.state.counter}</button>
223 }
224 handleClick() {
225 // Use callback in setState to manilate state before retuning it:
226 this.setState(state => {
227 if (state.counter < 10) {
228 return {counter: state.counter + 1}
229 }
230 })
231 }
232}
233
234// Create instance of button:
235// Clicking on the new button will encrease its value upto 10.
236const button = new Button()
237```
238
239Be aware that whatever the callback returns will be set as the component's new state. Therefore you must complete all the changes you need before returning it.
240
241## Keyed Items
242
243In 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, you'll want to use keyed data.
244
245## Keyed Array Data
246The 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).
247
248Below is an example of some keyed data:
249
250```javascript
251const fruits = [
252 {
253 id: 'a101',
254 name: 'Apple',
255 price: '1.00'
256 },
257 {
258 id: 'a102',
259 name: 'Orange',
260 price: '1.50'
261 },
262 {
263 id: 'a103',
264 name: 'Banana',
265 price: '2.00'
266 }
267]
268```
269
270To create a keyed list we need to provide each item in the list a `key` property. Notice how the following template does that:
271
272```javascript
273const fruitList = new Component({
274 root: '#fruits',
275 state: fruits,
276 render: (fruits) => (
277 <ul>
278 {
279 fruits.map(fruit => <li key={fruit.id}>{fruit.name}</li>)
280 }
281 </ul>
282 )
283})
284```
285
286The 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.
287
288## Keys Must Be Unique
289
290When 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.
291
292
293## Never Use Loop Index for Keys
294
295Never ever use loop indexes as data keys. The loop index value will change every time the data items are changes. This will make it impossible to understand the differences between the data and the DOM. This means you will get unpredictable DOM updates. Keys must always be in the data itself.
296
297Database as a rule provide unique ids for data items. Use those as keys.
298
299## State in Class Components
300
301When 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:
302
303```javascript
304class Clock extends Component {
305 constructor(opts) {
306 super(opts)
307 // Set state for all class instances:
308 this.state = {time: Date.now()
309 }
310 render() {
311 let time = this.state.time
312 const angle = (time) => 2 * Math.PI * time / 60000
313 return (
314 <li>
315 <div>
316 <svg viewBox="0 0 100 100" width="150px">
317 <circle cx="50" cy="50" r="45" fill="blue" />
318 <line
319 stroke-width="2"
320 x1="50"
321 y1="50"
322 x2={ 50 + 40 * Math.cos(angle(time)) }
323 y2={ 50 + 40 * Math.sin(angle(time)) }
324 stroke="white"
325 />
326 </svg>
327 </div>
328 </li>
329 )
330 }
331}
332```
333
334## Third Party State Management
335
336Because you can create stateless components, you can use thrid party state management solutions with Composi. Redux, Mobx, or roll your own.
337
338## Redux
339
340```javascript
341const { h, Component } from 'composi'
342const { createStore } from 'redux'
343
344// Reducer:
345function count(state=0, action) {
346 switch(action.type) {
347 case 'INCREMENT':
348 if (state > 99) return 100
349 return state + 1
350 case 'DECREMENT':
351 if (state < 1) return 0
352 return state - 1;
353 default:
354 return state
355 }
356}
357
358// Action Creators:
359const increment = () => {
360 return {
361 type: 'INCREMENT'
362 };
363};
364
365const decrement = () => {
366 return {
367 type: 'DECREMENT'
368 };
369};
370
371// Create Redux store:
372const store = createStore(count)
373
374
375// Extend Component to create counter:
376class Counter extends Component {
377 constructor(opts) {
378 super(opts)
379
380 // Assigning store to component:
381 this.store = opts.store
382
383 // Update component when store state changes:
384 store.subscribe(() => this.updateFromStore())
385
386 // Give counter default value of "0":
387 this.render = (count = 0) => (
388 <div id="counter">
389 <button id="dec" disabled={count==0} onclick={() => this.dec()}>-</button>
390 <span id="text">{count}</span>
391 <button id="inc" onclick={() => this.inc()}>+</button>
392 </div>
393 )
394 }
395
396 // Use ths method to udpate counter with state changes:
397 updateFromStore() {
398 const state = this.store.getState()
399 this.update(state)
400 }
401
402 inc() {
403 this.store.dispatch(increment())
404 }
405
406 dec() {
407 this.store.dispatch(decrement())
408 }
409}
410
411// Create new counter:
412const counter = new Counter({
413 root: 'article',
414 store: store
415})
416counter.update()
417```
418
419This 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.
420
421## Mobx
422
423You can also use [Mobx](https://mobx.js.org) for state management. Like in the Redux example above, you'll want to use state components.
424
425```javascript
426const {h, Component} from 'composi'
427const { observable, autorun } from 'mobx'
428
429const store = observable({ count: 0})
430
431// Extend Component to create counter:
432class Counter extends Component {
433 constructor(opts) {
434 super(opts)
435 this.root = 'article'
436
437 // Assign Mobx obersable store to Counter:
438 this.store = store
439
440 // Give counter default value of "0":
441 this.render = (count = 0) => (
442 <div id="counter">
443 <button id="dec" onclick={() => this.dec()} disabled={count==0}>-</button>
444 <span id="text">{count}</span>
445 <button id="inc" onclick={() => this.inc()} disabled={count>19}>+</button>
446 </div>
447 )
448 }
449
450 inc() {
451 this.store.count++
452 }
453
454 dec() {
455 this.store.count--
456 }
457}
458
459// Create new counter:
460const counter = new Counter()
461
462// Capture observable store changes and update counter:
463autorun(() => counter.update(counter.store.count))
464```
465
466Of 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.