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 = 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 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 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)
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"