UNPKG

5.09 kBMarkdownView Raw
1# State
2
3The output of a component is based on input properties passed from its parent as attributes. However, a component may also maintain internal state that it uses to control its view. If Marko detects a change to either input or to the internal state, the view will automatically be updated.
4
5> **ProTip:**
6> Only data that is owned and modified by the component should go into its `state`. State should be exclusively used for data that triggers rerenders. Parents control `input` the component controls its own `state`.
7
8## Initializing state
9
10To use `state` in Marko, you must first create a [class component](./class-components.md) and initialize the state within the [`onCreate`](./class-components.md#oncreateinput-out) method. In class methods, `this.state` may be used and within the template section, a `state` variable is available.
11
12```marko
13class {
14 onCreate() {
15 this.state = { count: 0 };
16 }
17}
18
19<div>The count is ${state.count}</div>
20```
21
22> **Note:** Only properties that exist when `this.state` is first defined will be watched for changes. If you don't need a property initially, you can set it to `null`.
23
24## Updating state
25
26You can update `state` in response to DOM events, browser events, ajax calls, etc. When a property on the state changes, the view will be updated to match.
27
28```marko
29class {
30 onCreate() {
31 this.state = { count: 0 };
32 }
33 increment() {
34 this.state.count++;
35 }
36}
37
38<div>The count is ${state.count}</div>
39<button on-click('increment')>Increment</button>
40```
41
42We've extended our example above to add a button with an [event handler](./events.md), so that, when clicked, the `state.count` value is incremented.
43
44> **Note:**
45> When browsing existing code, you may see `this.setState('name', value)` being used. This is equivalent to `this.state.name = value`.
46
47### How updates work
48
49When a property on `state` is set, the component will be scheduled for an update if the property has changed. All updates are batched together for performance. This means you can update multiple state properties at the same time without causing multiple updates.
50
51> **ProTip:** If you need to know when the update has been applied, you can use `this.once('update', fn)` within a component method.
52
53> **Note:** The state object only watches its properties one level deep. This means updates to nested properites on the state (e.g. `this.state.object.something = newValue`) will not be detected.
54>
55> Using [immutable](https://wecodetheweb.com/2016/02/12/immutable-javascript-using-es6-and-beyond/) data structures is recommended, but if you want to mutate a state property (perhaps push a new item into an array) you can let Marko know it changed using `setStateDirty`.
56>
57> ```js
58> this.state.numbers.push(num);
59>
60> // mark numbers as dirty, because a `push`
61> // won't be automatically detected by Marko
62> this.setStateDirty("numbers");
63> ```
64
65## Cross component state management
66
67There are various tools available to manage state outside of a single component. Here are some basic guidelines.
68
69Typically we recommend using `attributes` to pass data in to a child component, and children can [emit events](./events.md#emitting-custom-events) to communicate back up to their parents. In some cases this can become cumbersome with deeply nested data dependencies or global state.
70
71### Global/Subtree
72
73For passing state throughout a component tree without explicit attribute setting throughout the entire app, you can leverage the [`<context>`](https://github.com/marko-js/tags/tree/master/tags/context) tag. This tag can be [installed from npm](./custom-tags.md#using-tags-from-npm).
74
75This tag allows you to pull state from any level above in the tree and can also be used to pass global state throughout your app.
76Context providers can register event handlers that any child in the tree can trigger similar to the [events API](./events.md).
77
78_fancy-form.marko_
79
80```marko
81<context coupon=input.coupon on-buy(handleBuy)>
82 <!-- Somewhere nested in the container will be the buy button -->
83 <fancy-container/>
84</context>
85```
86
87_fancy-save-button.marko_
88
89```marko
90<context|{ coupon }, emit| from="fancy-form">
91 Coupon: ${coupon}.
92 <button on-click(emit, "buy")>Buy</button>
93</context>
94```
95
96> **Note:** Context _couples_ tags together and can limit reuse of components.
97
98### When to use a Redux like pattern
99
100Often the above two approaches are enough, and many people [jump to this part far too quickly](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367). Like `<context>`, often anything stored in redux is `global`. This means that it can (if abused) create components that are hard to reuse, reason about and test. However it is important to understand when a tool like `redux` is useful in any UI library.
101
102Redux provides indirection to updating any state that it controls. This is useful if you need the following:
103
104- Single state update, multiple actions (eg: logging, computed data, etc).
105- Time travel debugging and other [redux-specific tooling](https://redux.js.org/introduction/ecosystem).