You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A core concept behind React is that Components contain their own state and pass state down to their children as params. React re-renders the interface based on those state changes. Each Component is discreet and only needs to worry about how to render itself and pass state down to its children.
6
4
7
-
Sometimes though, at an application level, Components need to be able to share state in a way which breaks this parent-child relationship.
5
+
Sometimes however, at an application level, Components need to be able to share information or state in a way which does not adhere to this strict parent-child relationship.
8
6
9
7
Some examples of where this can be necessary are:
10
8
11
-
+ Where a child needs to pass a state change back to its parent (for example if the child component is an item in a list, and clicking on it needs to alter the page the parent is rendering).
9
+
+ Where a child needs to pass a message back to its parent. An example would be if the child component is an item in a list, it might need to inform it's parent that it has been clicked on.
12
10
13
-
+ When Hyperstack models are passed as params, child components might need to mutate the fields in the model, which will be rendered elsewhere on the page, causing re-rendering
11
+
+ When Hyperstack models are passed as params, child components might change the values of fields in the model, which might be rendered elsewhere on the page.
14
12
15
-
+Global application-level settings; like the ID of the currently logged in user or a preference or variable that affects the whole UI
13
+
+There has to be a place to store non-persisted, global application-level data; like the ID of the currently logged in user or a preference or variable that affects the whole UI.
16
14
17
15
Taking each of these examples, there are ways to accomplish each:
18
16
19
-
+ Child passing a message to parent: the easiest way is to pass a Proc as a param to the child from the parent that the child can `call` to pass a message back to the parent. This model works well with a list of items which need to inform their container if they have been selected (causing a re-render of the container). You can read more about Params of type Proc in the Component section of these docs.
17
+
+ Child passing a message to parent: the easiest way is to pass a `Proc` as a param to the child from the parent that the child can `call` to pass a message back to the parent. This model works well when there is a simple upward exchange of information (a child telling a parent that it has been selected for example). You can read more about Params of type Proc in the Component section of these docs. If howevere, you find yourself adding overusing this method, or passing messages from child to grandparent then you have reached the limits of this method and a Store would be a better option (read about Stores in this section.)
20
18
21
-
+ Models are stores. An instance of a model can be passed between Components, and any Component using the data in a Model to render the UI will re-render when the Model data changes. As an example, if you had a page displaying data from a Model and let's say you have an edit button on that page (which invokes a Dialog (Modal) based Component which receives the model as a param). As the user edits the Model fields in Dialog, the underlying page will show the changes as they are made as the changes to Model fields will be observed by the parent Components.
19
+
+ Models are stores. An instance of a model can be passed between Components, and any Component using the data in a Model to render the UI will re-render when the Model data changes. As an example, if you had a page displaying data from a Model and let's say you have an edit button on that page (which invokes a Dialog (Modal) based Component which receives the model as a param). As the user edits the Model fields in Dialog, the underlying page will show the changes as they are made as the changes to Model fields will be observed by the parent Components. In this way, Models act very much like Stores.
22
20
23
-
+Global, application wide state can exist in singleton classes that all Components can access. This is the core idea behind a store. A Store is a class or an instance of a Class which holds state variables which can affect a re-render of any Component observing that data.
21
+
+Stores are where global, application wide state can exist in singleton classes that all Components can access or as class instances objects which hold data and state. **A Store is a class or an instance of a class which holds state variables which can affect a re-render of any Component observing that data.**
24
22
25
23
In technical terms, a Store is a class that includes the `include Hyperstack::State::Observable` mixin, which just adds the `mutate` and `observe` primitive methods (plus helpers built on top of them).
26
24
@@ -32,6 +30,7 @@ As an example, let's imagine we have a filter field on a Menu Bar in our applica
32
30
# app/hyperstack/stores/item_store.rb
33
31
classItemStore
34
32
includeHyperstack::State::Observable
33
+
35
34
class << self
36
35
deffilter=(f)
37
36
mutate @filter= f
@@ -47,150 +46,80 @@ end
47
46
In Our application code, we would use the filter like this:
48
47
49
48
```ruby
50
-
# the text filed on the Menu Bar could look like this:
49
+
# the TextField on the Menu Bar could look like this:
51
50
TextField(label:'Filter', value:ItemStore.filter).on(:change) do |e|
52
51
ItemStore.filter = e.target.value
53
52
end
54
53
55
-
# elsewhere we could use the filter to decide if an item is added to a list
56
-
show_item(item) if item.name.to_s.upcase.include?(ItemStore.filter.upcase)
54
+
# elsewhere in the code we could use the filter to decide if an item is added to a list
55
+
show_item(item) if item.name.include?(ItemStore.filter)
57
56
```
58
57
59
58
## The observe and mutate methods
60
59
61
60
As with Components, you `mutate` an instance variable to notify React that the Component might need to be re-rendered based on the state change of that object. Stores are the same. When you `mutate` and instance variable in Store, all Components that are observing that variable will be re-rendered.
62
61
63
-
`observe` records that the current react Component being rendered has observed this object, and will need to be re-rendered if the object is mutated in the future.
64
-
65
-
If you `mutate` an instance variable outside of a Component, you need to `observe` it because, for simplicity, a Component observes itself.
62
+
`observe` records that a Component is observing an instance variable in a Store and might need to be re-rendered if the variable is mutated in the future.
66
63
67
-
Here is a very simple store that is just a global click counter
68
-
69
-
```ruby
70
-
classClick
71
-
includeHyperstack::State::Observable
72
-
class << self
73
-
defcount
74
-
observe @count||=0
75
-
end
64
+
> If you `mutate` an instance variable outside of a Component, you need to `observe` it because, for simplicity, a Component observe their own instance vaibales.
76
65
77
-
definc
78
-
mutate @count= count +1
79
-
end
66
+
The `observe` and `mutate` methods take:
80
67
81
-
defcount=(x)
82
-
mutate @count= x
83
-
end
84
-
defreset
85
-
mutate @count=0
86
-
end
87
-
end
88
-
end
89
-
```
68
+
+ a single param as shown above
69
+
+ a string of params (`mutate a=1, b=2`)
70
+
+ or a block in which case the entire block will be executed before signalling the rest of the system
71
+
+ no params (handy for adding to the end of a method)
90
72
91
-
Now any component can access and change the counter by calling `Click.count`, `Click.inc` and `Click.reset` as needed.
73
+
## Helper methods
92
74
93
-
The `observe` and `mutate` methods take no params (handy for adding to the end of a method), a single param as shown above, or a block in which case the entire block will be executed before signalling the rest of the system.
75
+
To make things easier the `Hyperstack::State::Observable` mixin contains some useful helper methods:
94
76
95
-
## Helper methods
77
+
The `observer` and `mutator`methods create a method wrapped in `observe` or `mutate` block.
96
78
97
-
To make things easier the `Hyperstack::State::Observable` mixin contains some additional helper methods:
79
+
+`observer`
80
+
+`mutator`
98
81
99
82
```ruby
100
-
classClick
101
-
includeHyperstack::State::Observable
102
-
class << self
103
-
observer(:count) { @count||=0 }
104
-
state_writer :count
105
-
mutator(:inc) { count = count +1 }
106
-
mutator(:reset) { count =0 }
107
-
end
108
-
end
83
+
mutator(:inc) { @count=@count+1 }
84
+
mutator(:reset) { @count=0 }
109
85
```
110
86
111
-
The `observer` and `mutator` methods create a method wrapped in `observe` or `mutate` block.
112
-
113
-
In addition there are `state_accessor`, `state_reader` and `state_writer` methods that work just like `attr_accessor` methods except access is wrapped in the appropriate `mutate` or `observe` method.
114
-
115
-
The methods can be used either at the class or instance level as needed.
87
+
The `state_accessor`, `state_reader` and `state_writer` methods work just like `attr_accessor` methods except access is wrapped in the appropriate `mutate` or `observe` method. These methods can be used either at the class or instance level as needed.
116
88
117
-
Because stateful components use the same `Observable` module all the above methods are available to help structure your
118
-
Components nicely. Notice in the component example we never use `observe` that is because by definition Components always `observe` their own state automatically so you don't need to.
89
+
+`state_reader`
90
+
+`state_writer`
91
+
+`state_accessor`
119
92
93
+
Finally there is the `toggle` method which does what it says on the tin.
a component then does a Clock.current_time and gets the current time, and is now observing the Clock.
138
-
a minute later the clock will wake up, and mutate @time, which will cause every component that has observed the clock to be rerendered.
110
+
### Initializing class variables in singleton Store
139
111
112
+
You can keep the logic around initialization in your Store. Remember that in Ruby your class instance variables can be initialized as the class is defined:
140
113
141
114
```ruby
142
-
classClickCounter
143
-
includeHyperstack::State::Observable
144
-
class << self
145
-
defclick!
146
-
mutate @click= (@click||0) +1
147
-
end
148
-
defclicks
149
-
observe @click
150
-
end
151
-
end
152
-
end
153
-
```
115
+
classCardStatusStore < HyperStore
154
116
155
-
there are helpers to make this read nicer:
117
+
@show_card_status=true
118
+
@show_card_details=false
156
119
157
-
```ruby
158
-
classClickCounter
159
-
includeHyperstack::State::Observable
160
120
class << self
161
-
mutator :click! { @click= (@click||0) +1 } # just calls mutate for you, but also makes it clear to the reader
162
-
state_reader :clicks# just like attr_reader but does an observe, also you have state_writer, and state_accessor
121
+
state_accessor :show_card_status
122
+
state_accessor :show_card_details
163
123
end
164
124
end
165
125
```
166
-
167
-
Mitch VanDuyn @catmando Mar 28 17:51
168
-
also there is the handy toggle method: toggle(:foo) === mutate @foo = !@foo
169
-
and that is about it. everything is based on the observe and mutate method so once you understand those the rest are easy.
170
-
and by the way for handiness observe and mutate's signature is like this
171
-
172
-
`def mutate(*args, &block)`
173
-
so you can say mutate @foo = 12, @bar = 77
174
-
throws all the args away except the last which it returns.
175
-
or you can say mutate do ... end if you need to evaluate a bunch of complex stuff and return the last value.
176
-
it all does the same thing, so its just sugar so the code looks nice.
177
-
178
-
Mitch VanDuyn @catmando Mar 28 17:56
179
-
Note you observe and mutate the object not any particular instance variable.
180
-
A final niceity (you will see this in the stock ticker) ... the system will take care of cleaning up things like timers when an object is no longer used. Not sure if you even want to document that.
181
-
182
-
Mitch VanDuyn @catmando Mar 28 23:28
183
-
In above examples instance variable would be better named @clicks
184
-
The state_reader :clicks
185
-
186
-
187
-
Or a better example:
188
-
189
-
```ruby
190
-
classClickCounter
191
-
includeHyperstack::State::Observable
192
-
class << self
193
-
mutator :click! { @click= (@click||0) +1 } # just calls mutate for you, but also makes it clear to the reader
194
-
state_reader :click# just like attr_reader but does an observe, also you have state_writer, and state_accessor
0 commit comments