Skip to content

Commit 57a7163

Browse files
committed
testing wip image display
1 parent 5e8d326 commit 57a7163

File tree

3 files changed

+6
-359
lines changed

3 files changed

+6
-359
lines changed

docs/client-dsl/lifecycle-methods.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ A component may define lifecycle methods for each phase of the components lifecy
1414

1515
All the Component Lifecycle methods (except `render`) may take a block or the name(s) of instance method(s) to be called. The `render` method always takes a block.
1616

17-
> The `rescues` callback is described **[here...](/error-recovery.md)**
18-
1917
```ruby
2018
class MyComponent < HyperComponent
2119
before_mount do
2220
# initialize stuff here
2321
end
2422

2523
render do
26-
# return just some rendered components
24+
# return some elements
2725
end
2826

2927
before_unmount :cleanup # call the cleanup method before unmounting

docs/client-dsl/notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Having an application wide `HyperComponent` class allows you to modify component
6666
> This is just a convention. Any class that includes the `Hyperstack::Component` module can be used as a Component. You also do not have
6767
to name it `HyperComponent`. For example some teams prefer `ApplicationComponent` more closely following the
6868
Rails convention. If you use a different name for this class be sure to set the `Hyperstack.component_base_class` setting so the
69-
Rails generators will use the proper name when generating your components. **[more details...](/rails-installation/generators.md#specifying-the-base-class)**
69+
Rails generators will use the proper name when generating your components. **[more details...](../rails-installation/generators.md#specifying-the-base-class)**
7070

7171
### Abstract and Concrete Components
7272

docs/hyper-state/README.md

Lines changed: 4 additions & 355 deletions
Original file line numberDiff line numberDiff line change
@@ -1,357 +1,6 @@
11

2-
### Param Equality
2+
<img align="left" width="100" height="100" style="margin-right: 20px" src="https://github.com/hyperstack-org/hyperstack/blob/edge/docs/wip.png?raw=true">
3+
**The `Hyperstack::State::Observable` module allows you to build
4+
classes that share their state with Hyperstack Components, and have those components update when objects in those classes change state.**
35

4-
Params can be arbitrarily
5-
6-
### Params, Immutability and State
7-
8-
Some care must be taken when passing params that are not simple scalars (string, numbers, etc) or JSON like
9-
combinations of arrays and hashes. When passing more complex objects
10-
11-
Hyperstack differs from React in how it deals with changes in param values
12-
13-
A component will re-render when new param values are received. If it appears that parameter values have changed,
14-
then the component will not re-render. For scalars such as strings and numbers and JSON like combinations of arrays
15-
and hashes, the component will be re-rendered if the value of the param changes.
16-
17-
For more complex objects such as application defined classes, there is generally no way to easily determine that an
18-
object's value has changed. Hyperstack solves this problem with the `Observable` class, that allows objects to
19-
track which components are depending on their values
20-
21-
In React \(and Hyperstack\) state is mutable. Changes \(mutations\) to state variables cause Components to re-render. Where state is passed into a child Component as a `param`, it will cause a re-rendering of that child Component. Change flows from a parent to a child - change does not flow upward and this is why params are not mutable.
22-
23-
State variables are normal instance variables or objects. When a state variable changes, we use the `mutate` method to get React's attention and cause a re-render. Like normal instance variables, state variables are created when they are first accessed, so there is no explicit declaration.
24-
25-
The syntax of `mutate` is simple - its `mutate` and any other number of parameters and/or a block. Normal evaluation means the parameters are going to be evaluated first, and then `mutate` gets called.
26-
27-
* `mutate @foo = 12, @bar[:zap] = 777` executes the two assignments first, then calls mutate
28-
* or you can say `mutate { @foo = 12; @bar[:zap] = 777 }` which is more explicit, and does the same thing
29-
30-
Here are some examples:
31-
32-
```ruby
33-
class Counter < HyperComponent
34-
before_mount do
35-
@count = 0 # optional initialization
36-
end
37-
38-
render(DIV) do
39-
# note how we mutate count
40-
BUTTON { "+" }.on(:click) { mutate @count += 1) }
41-
P { @count.to_s }
42-
end
43-
end
44-
```
45-
46-
```ruby
47-
class LikeButton < HyperComponent
48-
render(DIV) do
49-
BUTTON do
50-
"You #{@liked ? 'like' : 'haven\'t liked'} this. Click to toggle."
51-
end.on(:click) do
52-
mutate @liked = !@liked
53-
end
54-
end
55-
end
56-
```
57-
58-
### Components are Just State Machines
59-
60-
React thinks of UIs as simple state machines. By thinking of a UI as being in various states and rendering those states, it's easy to keep your UI consistent.
61-
62-
In React, you simply update a component's state, and then the new UI will be rendered on this new state. React takes care of updating the DOM for you in the most efficient way.
63-
64-
### What Components Should Have State?
65-
66-
Most of your components should simply take some params and render based on their value. However, sometimes you need to respond to user input, a server request or the passage of time. For this you use state.
67-
68-
**Try to keep as many of your components as possible stateless.** By doing this you'll isolate the state to its most logical place and minimize redundancy, making it easier to reason about your application.
69-
70-
A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via `param`s. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data in a declarative way.
71-
72-
State can be held in any object \(not just a Component\). For example:
73-
74-
```ruby
75-
class TestIt
76-
def self.swap_state
77-
@@test = !@@test
78-
end
79-
80-
def self.result
81-
@@test ? 'pass' : 'fail'
82-
end
83-
end
84-
85-
class TestResults < HyperComponent
86-
render(DIV) do
87-
P { "Test is #{TestIt.result}" }
88-
BUTTON { 'Swap' }.on(:click) do
89-
mutate TestIt::swap_state
90-
end
91-
end
92-
end
93-
```
94-
95-
In the example above, the singleton class `TestIt` holds its own internal state which is changed through a `swap_state` class method. The `TestResults` Component has no knowledge of the internal workings of the `TestIt` class.
96-
97-
When the BUTTON is pressed, we call `mutate`, passing the object which is being mutated. The actual mutated value is not important, it is the fact that the _observed_ object \(our `TestIt` class\) is being mutated that will cause a re-render of the _observing_ `TestResults` Component. Think about `mutate` as a way of telling React that the Component needs to be re-rendered as the state has changed.
98-
99-
In the example above, we could also move the _observing_ and _mutating_ behaviour out of the Component completely and manage it in the `TestIt` class - in this case, we would call it a Store. Stores are covered in the Hyper-Store documentation later.
100-
101-
### What Should Go in State?
102-
103-
**State should contain data that a component's instance variables, event handlers, timers, or http requests may change and trigger a UI update.**
104-
105-
When building a stateful component, think about the minimal possible representation of its state, and only store those properties in `state`. Add to your class methods to compute higher level values from your state variables. Avoid adding redundant or computed values as state variables as these values must then be kept in sync whenever state changes.
106-
107-
### What Shouldn't Go in State?
108-
109-
State should contain the minimal amount of data needed to represent your UI's state. As such, it should not contain:
110-
111-
* **Computed data:** Don't worry about precomputing values based on state — it's easier to ensure that your UI is consistent if you do all computation during rendering. For example, if you have an array of list items in state and you want to render the count as a string, simply render `"#{@list_items.length} list items'` in your `render` method rather than storing the count as another state.
112-
* **Data that does not effect rendering:** Changing an instance variable \(or any object\) that does not affect rendering does not need to be mutated \(i.e you do not need to call `mutate`\).
113-
114-
The rule is simple: anytime you are updating a state variable use `mutate` and your UI will be re-rendered appropriately.
115-
116-
### State and user input
117-
118-
Often in a UI you gather input from a user and re-render the Component as they type. For example:
119-
120-
```ruby
121-
class UsingState < HyperComponent
122-
123-
render(DIV) do
124-
# the button method returns an HTML element
125-
# .on(:click) is an event handeler
126-
# notice how we use the mutate method to get
127-
# React's attention. This will cause a
128-
# re-render of the Component
129-
button.on(:click) { mutate(@show = !@show) }
130-
DIV do
131-
input
132-
output
133-
easter_egg
134-
end if @show
135-
end
136-
137-
def button
138-
BUTTON(class: 'ui primary button') do
139-
@show ? 'Hide' : 'Show'
140-
end
141-
end
142-
143-
def input
144-
DIV(class: 'ui input fluid block') do
145-
INPUT(type: :text).on(:change) do |evt|
146-
# we are updating the value per keypress
147-
# using mutate will cause a rerender
148-
mutate @input_value = evt.target.value
149-
end
150-
end
151-
end
152-
153-
def output
154-
# rerender whenever input_value changes
155-
P { "#{@input_value}" }
156-
end
157-
158-
def easter_egg
159-
H2 {'you found it!'} if @input_value == 'egg'
160-
end
161-
end
162-
```
163-
164-
### State and HTTP responses
165-
166-
Often your UI will re-render based on the response to a HTTP request to a remote service. Hyperstack does not need to understand the internals of the HTTP response JSON, but does need to _observe_ the object holding that response so we call `mutate` when updating our response object in the block which executes when the HTTP.get promise resolves.
167-
168-
```ruby
169-
class FaaS < HyperComponent
170-
render(DIV) do
171-
BUTTON { 'faastruby.io' }.on(:click) do
172-
faast_ruby
173-
end
174-
175-
DIV(class: :block) do
176-
P { @hello_response['function_response'].to_s }
177-
P { "executed in #{@hello_response['execution_time']} ms" }
178-
end if @hello_response
179-
end
180-
181-
def faast_ruby
182-
HTTP.get('https://api.faastruby.io/paulo/hello-world',
183-
data: {time: true}
184-
) do |response|
185-
# this code executes when the promise resolves
186-
# notice that we call mutate when updating the state instance variable
187-
mutate @hello_response = response.json if response.ok?
188-
end
189-
end
190-
end
191-
```
192-
193-
### State and updating interval
194-
195-
One common use case is a component wanting to update itself on a time interval. It's easy to use the kernel method `every`, but it's important to cancel your interval when you don't need it anymore to save memory. Hyperstack provides Lifecycle Methods \(covered in the next section\) that let you know when a component is about to be created or destroyed. Let's create a simple mixin that uses these methods to provide a React friendly `every` function that will automatically get cleaned up when your component is destroyed.
196-
197-
```ruby
198-
module ReactInterval
199-
200-
def self.included(base)
201-
base.before_mount do
202-
@intervals = []
203-
end
204-
205-
base.before_unmount do
206-
@intervals.each(&:stop)
207-
end
208-
end
209-
210-
def every(seconds, &block)
211-
Kernel.every(seconds, &block).tap { |i| @intervals << i }
212-
end
213-
end
214-
215-
class TickTock < HyperComponent
216-
include ReactInterval
217-
218-
before_mount do
219-
@seconds = 0
220-
end
221-
222-
after_mount do
223-
every(1) { mutate @seconds = @seconds + 1 }
224-
end
225-
226-
render(DIV) do
227-
P { "Hyperstack has been running for #{@seconds} seconds" }
228-
end
229-
end
230-
```
231-
232-
Notice that TickTock effectively has two `before_mount` methods, one that is called to initialize the `@intervals` array and another to initialize `@seconds`
233-
234-
235-
236-
# Stores
237-
238-
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.
239-
240-
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.
241-
242-
Some examples of where this can be necessary are:
243-
244-
* 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.
245-
* 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.
246-
* 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.
247-
248-
Taking each of these examples, there are ways to accomplish each:
249-
250-
* 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.\)
251-
* 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.
252-
* 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.**
253-
254-
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\).
255-
256-
In most cases, you will want class level instance variables that share data across components. Occasionally you might need multiple instances of a store that you can pass between Components as params \(much like a Model\).
257-
258-
As an example, let's imagine we have a filter field on a Menu Bar in our application. As the user types, we want the user interface to display only the items which match the filter. As many of the Components on the page might depend on the filter, a singleton Store is the perfect answer.
259-
260-
```ruby
261-
# app/hyperstack/stores/item_store.rb
262-
class ItemStore
263-
include Hyperstack::State::Observable
264-
265-
class << self
266-
def filter=(f)
267-
mutate @filter = f
268-
end
269-
270-
def filter
271-
observe @filter || ''
272-
end
273-
end
274-
end
275-
```
276-
277-
In Our application code, we would use the filter like this:
278-
279-
```ruby
280-
# the TextField on the Menu Bar could look like this:
281-
TextField(label: 'Filter', value: ItemStore.filter).on(:change) do |e|
282-
ItemStore.filter = e.target.value
283-
end
284-
285-
# elsewhere in the code we could use the filter to decide if an item is added to a list
286-
show_item(item) if item.name.include?(ItemStore.filter)
287-
```
288-
289-
## The observe and mutate methods
290-
291-
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.
292-
293-
`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.
294-
295-
> 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.
296-
297-
The `observe` and `mutate` methods take:
298-
299-
* a single param as shown above
300-
* a string of params \(`mutate a=1, b=2`\)
301-
* or a block in which case the entire block will be executed before signalling the rest of the system
302-
* no params \(handy for adding to the end of a method\)
303-
304-
## Helper methods
305-
306-
To make things easier the `Hyperstack::State::Observable` mixin contains some useful helper methods:
307-
308-
The `observer` and `mutator` methods create a method wrapped in `observe` or `mutate` block.
309-
310-
* `observer`
311-
* `mutator`
312-
313-
```ruby
314-
mutator(:inc) { @count = @count + 1 }
315-
mutator(:reset) { @count = 0 }
316-
```
317-
318-
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.
319-
320-
* `state_reader`
321-
* `state_writer`
322-
* `state_accessor`
323-
324-
Finally there is the `toggle` method which does what it says on the tin.
325-
326-
* `toggle` toggle\(:foo\) === mutate @foo = !@foo
327-
328-
```ruby
329-
class ClickStore
330-
include Hyperstack::State::Observable
331-
332-
class << self
333-
observer(:count) { @count ||= 0 }
334-
state_writer :count
335-
mutator(:inc) { @count = @count + 1 }
336-
mutator(:reset) { @count = 0 }
337-
end
338-
end
339-
```
340-
341-
### Initializing class variables in singleton Store
342-
343-
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:
344-
345-
```ruby
346-
class CardStore
347-
include Hyperstack::State::Observable
348-
349-
@show_card_status = true
350-
@show_card_details = false
351-
352-
class << self
353-
state_accessor :show_card_status
354-
state_accessor :show_card_details
355-
end
356-
end
357-
```
6+
## This Page Under Construction

0 commit comments

Comments
 (0)