Skip to content

Commit ea6b988

Browse files
Store docs updated
1 parent caf88d1 commit ea6b988

File tree

1 file changed

+44
-115
lines changed

1 file changed

+44
-115
lines changed

docs/dsl-client/hyper-store.md

Lines changed: 44 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
11
# Stores
22

3-
**DRAFT DOCS**
4-
53
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.
64

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.
86

97
Some examples of where this can be necessary are:
108

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.
1210

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.
1412

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.
1614

1715
Taking each of these examples, there are ways to accomplish each:
1816

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.)
2018

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.
2220

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.**
2422

2523
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).
2624

@@ -32,6 +30,7 @@ As an example, let's imagine we have a filter field on a Menu Bar in our applica
3230
# app/hyperstack/stores/item_store.rb
3331
class ItemStore
3432
include Hyperstack::State::Observable
33+
3534
class << self
3635
def filter=(f)
3736
mutate @filter = f
@@ -47,150 +46,80 @@ end
4746
In Our application code, we would use the filter like this:
4847

4948
```ruby
50-
# the text filed on the Menu Bar could look like this:
49+
# the TextField on the Menu Bar could look like this:
5150
TextField(label: 'Filter', value: ItemStore.filter).on(:change) do |e|
5251
ItemStore.filter = e.target.value
5352
end
5453

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)
5756
```
5857

5958
## The observe and mutate methods
6059

6160
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.
6261

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.
6663

67-
Here is a very simple store that is just a global click counter
68-
69-
```ruby
70-
class Click
71-
include Hyperstack::State::Observable
72-
class << self
73-
def count
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.
7665
77-
def inc
78-
mutate @count = count + 1
79-
end
66+
The `observe` and `mutate` methods take:
8067

81-
def count=(x)
82-
mutate @count = x
83-
end
84-
def reset
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)
9072

91-
Now any component can access and change the counter by calling `Click.count`, `Click.inc` and `Click.reset` as needed.
73+
## Helper methods
9274

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:
9476

95-
## Helper methods
77+
The `observer` and `mutator` methods create a method wrapped in `observe` or `mutate` block.
9678

97-
To make things easier the `Hyperstack::State::Observable` mixin contains some additional helper methods:
79+
+ `observer`
80+
+ `mutator`
9881

9982
```ruby
100-
class Click
101-
include Hyperstack::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 }
10985
```
11086

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.
11688

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`
11992

93+
Finally there is the `toggle` method which does what it says on the tin.
12094

121-
--------------------------------------------------------------------
122-
Other text....
95+
+ `toggle` toggle(:foo) === mutate @foo = !@foo
12396

12497
```ruby
125-
class Clock
98+
class ClickStore
12699
include Hyperstack::State::Observable
100+
127101
class << self
128-
def current_time
129-
@time ||= Time.now
130-
@timer ||= every(60.seconds) { mutate @time = Time.now }
131-
observe @time
132-
end
102+
observer(:count) { @count ||= 0 }
103+
state_writer :count
104+
mutator(:inc) { @count = @count + 1 }
105+
mutator(:reset) { @count = 0 }
133106
end
134107
end
135108
```
136109

137-
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
139111

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:
140113

141114
```ruby
142-
class ClickCounter
143-
include Hyperstack::State::Observable
144-
class << self
145-
def click!
146-
mutate @click = (@click || 0) + 1
147-
end
148-
def clicks
149-
observe @click
150-
end
151-
end
152-
end
153-
```
115+
class CardStatusStore < HyperStore
154116

155-
there are helpers to make this read nicer:
117+
@show_card_status = true
118+
@show_card_details = false
156119

157-
```ruby
158-
class ClickCounter
159-
include Hyperstack::State::Observable
160120
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
163123
end
164124
end
165125
```
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-
class ClickCounter
191-
include Hyperstack::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
195-
end
196-
```

0 commit comments

Comments
 (0)