Hyperstack::State::Observable
module.DisplayGame
component. State was updated by "bubbling up" events from lower level components up to DisplayGame
where the event handler updated the state.DisplayGame
component is responsible for both managing state and displaying the game.Hyperstack::State::Observable
module allows us to put the game's state into a separate class which can be accessed from any component: No more need to bubble up events, and no more cluttering up our DisplayGame
component with state management and details of the game's data structure.DisplayGame
component into its own class:DisplayGame
component.Game
is now in its own class and includes Hyperstack::State::Observable
. This adds a number of methods to Game
that allows our class to become a reactive store. When Game
interacts with other stores and components they will be updated as the state of Game
changes.@history
and @step
in the before_mount
callback. The same initialization is now in the initialize
method which will be called when a new instance of the game is created. This will still be done in the DisplayGame
before_mount
callback (see below.)player
and current
. Now that Game
is a separate class we define these methods using observer
.observer
method creates a method that is the inverse of mutator
. While mutate
(and mutator
) indicate that state has been changed observe
and observer
indicate that state has been accessed outside the class.mutate
, mutator
, and state_writer
, we have observe
, observer
, and state_reader
.current_winner?
. It accesses the internal state through the current
method so there is no need to explicitly make current_winner?
an observer (but we could, without affecting anything.)handle_click!
and jump_to!
mutators either.DisplayGame
before_mount
callback is still responsible for initializing the game, but it no longer needs to be aware of the internals of the game's state. It simply calls Game.new
and stores the result in the @game
instance variable. For the rest of the component's code we call the appropriate method on @game
.DisplayBoard
(we will see why shortly) so we will rename it to DisplayCurrentBoard
.DisplayCurrentBoard
will be responsible for directly notifying the game that a user has clicked, so we do not need to handle any events coming back from DisplayCurrentBoard
.DisplayCurrentBoard
component receives the entire game, and it will access the current board, using the current
method, and will directly notify the game when a user clicks using the handle_click!
method.DisplayCurrentBoard
directly deal with user actions, we simplify both components as they do not have to communicate back upwards via events. Instead we communicate through the central game store.Game
store holds the state, the top level component reads the state and sends it down to lower level components, those components update the Game
state causing the top level component to re-rerender.current_winner?
now are neatly abstracted out into their own class.Game
with class methods like this:Game
throughout.Hyperstack::State::Observable
module will call any class level initialize
methods in the class or subclasses before the first component mounts.DisplayBoard
as DisplayBoard
can directly access Game.handle_click!
since there is only one game.observe
and mutate
methods marks when its internal data has been observed by some other class, or when its internal data has changed.observe
, observer
, state_reader
or state_accessor
methods. Likewise a method that changes internal state must declare this using mutate
, mutator
, state_writer
or state_accessor
methods.mutate
or observe
method.DisplayBoard
to demonstrate this:DisplayBoard
no longer takes any parameter (and could be renamed again to DisplayCurrentBoard
) and now a new component - DisplaySquare
- takes the id of the square to display, but the game or the current board are never passed as parameters; there is no need to as they are implicit.observe, observer, state_reader
observe
method takes any number of arguments and/or a block. The last argument evaluated or the value of the block is returned.observer
method defines a new method with an implicit observe:return
or break
the state will not be observed.state_reader
method declares one or more state accessors with an implicit state observation:mutate, mutator, state_writer, toggle
mutate
method takes any number of arguments and/or a block. The last argument evaluated or the value of the block is returned.mutator
method defines a new method with an implicit mutate:return
or break
the state will not be mutated.state_writer
method declares one or more state accessors with an implicit state mutation:toggle
method reverses the polarity of a instance variable:state_accessor
Methodstate_reader
and state_writer
methods.HyperComponent
base class includes Hyperstack::State::Observable
so any HyperComponent
has access to all of the above methods. A component also always observes itself so you never need to use observe
within a component unless the state will be accessed outside the component. However once you start doing that you would be better off to move the state into a separate store.In addition components also act as the Observers in the system. What this means is that the current component that is running its render method is recording all stores that callobserve
, when a store mutates, then all the components that recorded observations will be rerendered.