See live here
This demonstrates "reactivity" between Component
in miso
.
----------------------------------------------------------------------------
childComponent :: MisoString -> Component ParentModel ChildModel ChildAction
childComponent childComponentName = (component (ChildModel 0) noop view_)
{ bindings =
[ parentField <---> childField
-- ^ dmj: Bidirectional synch between parent and child `model`, using `Lens`
]
} where
view_ :: ChildModel -> View ChildModel ChildAction
view_ (ChildModel x) =
div_
[]
[ h3_ [] [ text ("Child Component " <> childComponentName) ]
, button_ [ onClick ChildAdd ] [ "+" ]
, text (ms x)
, button_ [ onClick ChildSubtract ] [ "-" ]
]
As of 1.9
, miso
is now recursive. This means miso
applications can embed other miso
applications, and be distributed independently. The type Component
has been introduced to facilitate this, and is equipped with lifecycle mounting hooks (mount
/ unmount
). This has necessitated a runtime system to manage Component
internally.
This means miso
now forms a graph of Component
nested on the Virtual DOM, where each Component
has its own IORef model
state (a.k.a. "reactive variable") that can be synchronized between the parent / child relationship (unidirectionally or bidirectionally) in a type-safe, composable manner.
miso
has added the bindings
field to establish edges in the Component
graph (between immediate ancestor and descendant). This allows data to "pulsate" between Component
keeping data in synch. When used at multiple levels in the tree this creates a cascade effect.
The -->
, <--
, <-->
reactive combinators have been introduced to allow users to establish edges between Component
in the graph, in a declarative way. This creates dependencies in the graph between Component
model
changes. The combinators take two Lens
as arguments, which synchronize changes between Component
model
in the direction the user desires.
Under the hood this is done through a broadcast TChan
, to synchronize the IORef model
of various Component
. This is accomplished without imposing a recursive interface on end users (miso
handles all the recursion under the hood).
This is similar to React props, where a parent component can pass properties to its descendants, and they will inherit any changes the parent makes to that "prop". The difference with miso
is that we accomplish this in a declarative way using Lens
to synchronize state. This allows us to keep the View
pure, and retain the isomorphism property. Furthermore, miso
takes it a step further and allows declarative upstream communication with the parent
. Whereas in React a callback would need to be passed to the child to invoke parent model changes, creating a more convoluted programming model. A bidirectional synch can also be established between parent
and child
using the (<-->)
combinator. This can allow sibling communication, where the parent
is used as a proxy (as seen in the example).
Lastly, this is all done in a type-safe way. Component
is now parameterized by parent
, which is the type of the ancestor's model
("reactive variable"). This gives us type-safe, reactive Component
composition.
The source maintains an example of sibling communication using the <-->
reactive combinator.
Tip
This requires installing nix with Nix Flakes enabled. Although not required, we recommend using miso's binary cache.
Call nix develop
to enter a shell with GHC 9.12.2
$ nix develop --experimental-features nix-command --extra-experimental-features flakes
Once in the shell, you can call cabal run
to start the development server and view the application at http://localhost:8080
$ nix develop .#wasm --command bash -c "make"
$ nix develop .#ghcjs --command bash -c "build"
To host the built application you can call serve
$ nix develop .#wasm --command bash -c "serve"
$ nix develop .#wasm --command bash -c "make clean"
This comes with a GitHub action that builds and auto hosts the example.