Core concepts

Subscriptions

A Sub is any long-running operation that is external to a component but that can write to the component's Sink. As of 1.14.0 it is also handed an IO model, to read the component's current model on demand. Subs come in two flavours: the static subs list and dynamic subs via startSub / stopSub.

type Sub model action =
  Sink action -> IO model -> IO ()

subs

main :: IO ()
main = startApp defaultEvents app { subs = [ timerSub ] }

timerSub :: Sub Model Action
timerSub sink _readModel =
  forever $ threadDelay 100000 >> sink Log

data Action = Log

The subs field contains subs that exist for the lifetime of the component. When it unmounts, they are stopped and their resources finalised. Here is a real one from Miso.Subscription.OnLine:

onLineSub :: (Bool -> action) -> Sub model action
onLineSub f sink _readModel = createSub acquire release sink
  where
    release (cb1, cb2) = do
      windowRemoveEventListener "online"  cb1
      windowRemoveEventListener "offline" cb2
    acquire = do
      cb1 <- windowAddEventListener "online"
        (const $ sink (f True))
      cb2 <- windowAddEventListener "offline"
        (const $ sink (f False))
      pure (cb1, cb2)

startSub / stopSub

At times it is necessary to dynamically create a sub in response to an event (e.g. starting a Miso.WebSocket connection when a user logs in):

update = \case
  StartTimer -> startSub ("timer" :: MisoString) timerSub
  StopTimer  -> stopSub "timer"
  Log        -> io_ (consoleLog "log")
  where
    timerSub :: Sub Model Action
    timerSub sink _ =
      forever $ threadDelay 100000 >> sink Log

createSub

Miso.Subscription.Util.createSub builds a sub using the bracket pattern, ensuring listeners are unregistered when the component unmounts. Use it only when custom event listeners are required; the Miso.Subscription modules cover the usual suspects:

Miso.Subscription.Mouse
global pointer position
Miso.Subscription.Keyboard
arrows, WASD, arbitrary key sets
Miso.Subscription.Window
resize, scroll, any window event via windowSub
Miso.Subscription.History
uriSub / routerSub for navigation
Miso.Subscription.RAF
rAFSubrequestAnimationFrame ticks for 60 FPS animation
Miso.Subscription.OnLine
navigator.onLine

Try it

A dynamic subscription with startSub / stopSublive
0.0s
data TimerModel = TimerModel
  { _ticks   :: Int
  , _running :: Bool
  } deriving (Show, Eq)

ticks :: Lens TimerModel Int
ticks = lens _ticks $ \m x -> m { _ticks = x }

running :: Lens TimerModel Bool
running = lens _running $ \m x -> m { _running = x }

data TimerAction = Start | Stop | Ticked

timer
  :: Component ctx () TimerModel TimerAction
timer = component (TimerModel 0 False) update view
  where
    tenTimesASecond :: Sub TimerModel TimerAction
    tenTimesASecond sink _ =
      forever (threadDelay 100000 >> sink Ticked)

    update = \case
      Start -> do
        running .= True
        startSub ("timer" :: MisoString) tenTimesASecond
      Stop -> do
        running .= False
        stopSub ("timer" :: MisoString)
      Ticked ->
        ticks += 1

    view m =
      H.div_ [ HP.class_ "row" ]
        [ H.button_ [ HE.onClick toggle ] [ text label ]
        , H.strong_ []
            [ let (secs, tenths) = (m ^. ticks) `divMod` 10
              in text (ms secs <> "." <> ms tenths <> "s")
            ]
        ]
      where
        toggle = if m ^. running then Stop else Start
        label  = if m ^. running then "Stop" else "Start"