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 = LogThe 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 LogcreateSub
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.HistoryuriSub/routerSubfor navigationMiso.Subscription.RAFrAFSub—requestAnimationFrameticks for 60 FPS animationMiso.Subscription.OnLinenavigator.onLine
Try it
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")
]
]