miso

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. Subs come in two flavours: the static subs list and dynamic subs via startSub / stopSub.

type Sub action = Sink action -> IO ()

subs

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

timerSub :: Sub Action
timerSub sink = 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 action
onLineSub f sink = 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 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)

data TimerAction = Start | Stop | Ticked

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

    update = \case
      Start  -> do
        modify (\m -> m { _running = True })
        startSub ("timer" :: MisoString) tenTimesASecond
      Stop   -> do
        modify (\m -> m { _running = False })
        stopSub ("timer" :: MisoString)
      Ticked -> modify (\m -> m { _ticks = _ticks m + 1 })

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