UNPKG

14.5 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 and Render](./render.md)
12- [Components](./components.md)
13- State
14- [Lifecycle Methods](./lifecycle.md)
15- [Events](./events.md)
16- [Styles](./styles.md)
17- [Unmount](./unmount.md)
18- [Third Party Libraries](./third-party.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
117const fruitList = new Component({
118 container: 'main',
119 state: ['Apples', 'Oranges', 'Pinapplez', 'Bananas'],
120 render: (fruits) => {
121 return (
122 <ul>
123 {
124 fruits.map(fruit => <li>{fruit}</li>)
125 }
126 </ul>
127 )
128 }
129})
130
131// Use second argument for index in the array you want to update:
132fruitList.setState('Pinapples', 2)</code>
133```
134
135### Arrays of Objects
136Arrays 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:
137
138```javascript
139const people = [
140 {
141 firstName: 'Joe',
142 lastName: 'Bodoni',
143 job: 'Mechanic'
144 },
145 {
146 firstName: 'Ellen',
147 lastName: 'Vanderbilt',
148 job: 'Lab Technician'
149 },
150 {
151 firstName: 'Sam',
152 lastName: 'Anderson',
153 job: 'Web Developer'
154 }
155]
156```
157This 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:
158
159```javascript
160// Update job for Joe Bodoni:
161userList.setState({job: 'Rock Star'}, 0)
162```
163
164The 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:
165
166```javascript
167// Proper way to update state. Use setState to access the index of array, make change and return it:
168userList.setState(prevState => prevState[0].job = 'Rock Star')
169```
170
171Complex State Operations
172------------------------
173
174As 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:
175
176```javascript
177// Use setState with a callback to get the state, reverse it and return it:
178fruitsList.setState(prevState => prevState.reverse())
179```
180
181setState with a Callback
182------------------------
183When 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.
184
185```javascript
186class Button extends Component {
187 constructor(props) {
188 super(props)
189 this.state = { counter: 1 }
190 }
191 render() {
192 return <button onclick={() => this.handleClick()}>{this.state.counter}</button>
193 }
194 handleClick() {
195 // Use callback in setState to manilate state before retuning it:
196 this.setState(state => {
197 if (state.counter < 10) {
198 return {counter: state.counter + 1}
199 }
200 })
201 }
202}
203
204// Create instance of button:
205// Clicking on the new button will encrease its value upto 10.
206const button = new Button()
207```
208
209Be 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.
210
211Keyed Items
212-----------
213
214In 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.
215
216### Keyed Array Data
217The 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).
218
219Below is an example of some keyed data:
220
221```javascript
222const fruits = [
223 {
224 id: 'a101',
225 name: 'Apple',
226 price: '1.00'
227 },
228 {
229 id: 'a102',
230 name: 'Orange',
231 price: '1.50'
232 },
233 {
234 id: 'a103',
235 name: 'Banana',
236 price: '2.00'
237 }
238]
239```
240
241To create a keyed list we need to provide each item in the list a `key` property. Notice how the following template does that:
242
243```javascript
244const fruitList = new Component({
245 root: '#fruits',
246 state: fruits,
247 render: (fruits) => (
248 <ul>
249 {
250 fruits.map(fruit => <li key={fruit.id}>{fruit.name}</li>)
251 }
252 </ul>
253 )
254})
255```
256
257The 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.
258
259Keys Must Be Unique
260-------------------
261When 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.
262
263State in Extended Components
264----------------------------
265
266When 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:
267
268```javascript
269class Clock extends Component {
270 constructor(opts) {
271 super(opts)
272 // Set state for all class instances:
273 this.state = {time: Date.now()
274 }
275 render() {
276 let time = this.state.time
277 const angle = (time) => 2 * Math.PI * time / 60000
278 return (
279 <li>
280 <div>
281 <svg viewBox="0 0 100 100" width="150px">
282 <circle cx="50" cy="50" r="45" fill="blue" />
283 <line
284 stroke-width="2"
285 x1="50"
286 y1="50"
287 x2={ 50 + 40 * Math.cos(angle(time)) }
288 y2={ 50 + 40 * Math.sin(angle(time)) }
289 stroke="white"
290 />
291 </svg>
292 </div>
293 </li>
294 )
295 }
296}
297```
298
299
300Third Party State Management
301----------------------------
302Because you can create stateless components, you can use thrid party state management solutions with Composi. Redux, Mobx, or roll your own.
303
304Redux
305-----
306
307```javascript
308const { h, Component } from 'composi'
309const { createStore } from 'redux'
310
311// Reducer:
312function count(state=0, action) {
313 switch(action.type) {
314 case 'INCREMENT':
315 if (state > 99) return 100
316 return state + 1
317 case 'DECREMENT':
318 if (state < 1) return 0
319 return state - 1;
320 default:
321 return state
322 }
323}
324
325// Action Creators:
326const increment = () => {
327 return {
328 type: 'INCREMENT'
329 };
330};
331
332const decrement = () => {
333 return {
334 type: 'DECREMENT'
335 };
336};
337
338// Create Redux store:
339const store = createStore(count)
340
341
342// Extend Component to create counter:
343class Counter extends Component {
344 constructor(opts) {
345 super(opts)
346
347 // Assigning store to component:
348 this.store = opts.store
349
350 // Update component when store state changes:
351 store.subscribe(() => this.updateFromStore())
352
353 // Give counter default value of "0":
354 this.render = (count = 0) => (
355 <div id="counter">
356 <button id="dec" disabled={count==0} onclick={() => this.dec()}>-</button>
357 <span id="text">{count}</span>
358 <button id="inc" onclick={() => this.inc()}>+</button>
359 </div>
360 )
361 }
362
363 // Use ths method to udpate counter with state changes:
364 updateFromStore() {
365 const state = this.store.getState()
366 this.update(state)
367 }
368
369 inc() {
370 this.store.dispatch(increment())
371 }
372
373 dec() {
374 this.store.dispatch(decrement())
375 }
376}
377
378// Create new counter:
379const counter = new Counter({
380 root: 'article',
381 store: store
382})
383counter.update()
384```
385
386This 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.
387
388Mobx
389----
390
391You can also use [Mobx](https://mobx.js.org) for state management. Like in the Redux example above, you'll want to use state components.
392
393```javascript
394const {h, Component} from 'composi'
395const { observable, autorun } from 'mobx'
396
397const store = observable({ count: 0})
398
399// Extend Component to create counter:
400class Counter extends Component {
401 constructor(opts) {
402 super(opts)
403 this.root = 'article'
404
405 // Assign Mobx obersable store to Counter:
406 this.store = store
407
408 // Give counter default value of "0":
409 this.render = (count = 0) => (
410 <div id="counter">
411 <button id="dec" onclick={() => this.dec()} disabled={count==0}>-</button>
412 <span id="text">{count}</span>
413 <button id="inc" onclick={() => this.inc()} disabled={count>19}>+</button>
414 </div>
415 )
416 }
417
418 inc() {
419 this.store.count++
420 }
421
422 dec() {
423 this.store.count--
424 }
425}
426
427// Create new counter:
428const counter = new Counter()
429
430// Capture observable store changes and update counter:
431autorun(() => counter.update(counter.store.count))
432```
433
434Of 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.