1 | # State
|
2 |
|
3 | The 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 |
|
10 | To 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
|
13 | class {
|
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 |
|
26 | You 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
|
29 | class {
|
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 |
|
42 | We'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 |
|
49 | When 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 |
|
67 | There are various tools available to manage state outside of a single component. Here are some basic guidelines.
|
68 |
|
69 | Typically 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 |
|
73 | For 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 |
|
75 | This 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.
|
76 | Context 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 |
|
100 | Often 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 |
|
102 | Redux 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).
|