miso

Core concepts

Communication

miso provides four mechanisms for components to exchange data.

  • Props — synchronous, parent-to-child read-only data passed at mount time; see Props.
  • Context — global data shared by the entire tree; any component can mutate it via modifyContext and opt in to re-renders with useContext; see Context.
  • Mailbox — message passing via mail, broadcast, checkMail; any component can send a JSON Value to any other by ComponentId.
  • PubSub (Miso.PubSub) — publish / subscribe for fan-out messaging across unrelated components.

The mailbox

Every component has a mailbox — a slot that receives Value messages sent by other components. Messages are dispatched asynchronously via the event queue.

Sending

mail componentId msg
send to a specific ComponentId (obtained via ask inside Effect)
mailParent msg
send to the direct parent
mailChildren msg
send to all immediate children
mailAncestors msg
walk up the hierarchy, delivering to every ancestor
mailDescendants msg
walk down the hierarchy, delivering to every descendant
broadcast msg
deliver to every mounted component except the sender

Receiving with checkMail

Wire up the mailbox field with checkMail, which handles JSON parsing and routes to success / error actions:

data Action
  = ReceivedMsg MyMsg
  | MailError   MisoString

myComp :: Component context props model Action
myComp = (vcomp m u v) { mailbox = checkMail ReceivedMsg MailError }

Looking up a ComponentId

update = \case
  SendMsg targetId -> io_ (mail targetId ("hello" :: MisoString))
  GetMyId -> do
    info <- ask
    let myId = _componentInfoId info
    ...

PubSub

Miso.PubSub provides topics: a component subscribes to a topic (receiving messages as actions) and any component may publish to it. It is the right tool when the sender does not know who is listening.

notifications :: Topic Note          -- a typed topic; Note has ToJSON / FromJSON
notifications = topic "notifications"

update = \case
  Init         -> subscribe notifications Notified NotifyError
  Notify n     -> io_ (publish notifications n)
  Notified n   -> ...                -- n :: Note
  NotifyError _ -> pure ()

Try it

Mailbox and PubSub between siblingslive
    -- Two siblings that do not know each other talk over a PubSub topic; the
    -- publisher also mails its parent, which relays the message to every child.
    data Note = Note MisoString
      deriving (Show, Eq, Generic, ToJSON, FromJSON)
    
    notes :: Topic Note
    notes = topic "demo-notes"
    
    data ChatAction = Relayed Note | MailErr MisoString
    
    chat :: Eq ctx => Component ctx () () ChatAction
    chat = (component () update view)
      { mailbox = checkMail Relayed MailErr }
      where
        update = \case
          Relayed n -> mailChildren n                -- parent → all of its children
          MailErr _ -> pure ()
    
        view _ () _ =
          H.div_ [ HP.class_ "cols" ]
            [ "publisher"  +> publisher
            , "subscriber" +> subscriber
            ]
    
    data PubAction = Send | Draft MisoString
    
    publisher :: Component ctx () MisoString PubAction
    publisher = component "hello from the publisher" update view
      where
        update = \case
          Draft s -> this .= s
          Send -> do
            s <- get
            io_ (publish notes (Note s))              -- fan out over the topic (IO)
            mailParent (Note s)                       -- and tell the parent directly
    
        view _ () s =
          H.div_ []
            [ H.input_ [ HP.value_ s, HE.onInput Draft ]
            , H.button_ [ HE.onClick Send ] [ "publish" ]
            ]
    
    data SubAction = Subscribe | Got Note | Oops MisoString
    
    subscriber :: Component ctx () [MisoString] SubAction
    subscriber = (component [] update view)
      { mount   = Just Subscribe                      -- subscribe on mount
      , mailbox = checkMail Got Oops }                -- also accepts parent mail
      where
        update = \case
          Subscribe   -> subscribe notes Got Oops
          Got (Note s) -> this %= take 5 . (s :)
          Oops _      -> pure ()
    
        view _ () received =
          H.ul_ [ HP.class_ "log" ] [ H.li_ [] [ text s ] | s <- received ]

    The publisher does two things on send: it publishes to a topic the subscriber joined on mount, and it mailParents the parent, which relays with mailChildren — so each message arrives at the subscriber twice, once by each route.