Elmish.Subscription
- Package
- purescript-elmish
- Repository
- collegevine/purescript-elmish
A facade API for expressing the idea of "external events" - i.e. messages
that do not originate in the UI itself, but come from other sources, such
as network, browser URL history manipulations, timer, JS workers, and so
on. The API in this module allows capturing such external events and
converting them to Elmish messages, so they can be handled in the UI
component's update
function.
A Subscription
value represents such an "external event" packaged for
reuse, while subscribe
and subscribeMaybe
functions allow to "start"
such subscription by including it in a state transition - usually the
initial one. When the component is destroyed (in React lingo -
"unmounted"), all its subscriptions are terminated.
Example:
-- A reusable library
module Location where
urlUpdates :: ∀ m. MonadEffect m => Subscription m { path :: String, query :: String }
urlUpdates = Subscription \dispatch -> do
listener <- eventListener \_ -> do
path <- window >>= location >>= pathname
query <- window >>= location >>= search
dispatch { path, query }
window <#> toEventTarget >>= addEventListener popstate listener false
pure $
window <#> toEventTarget >>= removeEventListener popstate listener false
-- Consuming code
type State = ...
data Message
= UrlChanged { path :: String, query :: String }
| ...
myComponent :: ComponentDef Message State
myComponent = { init, view, update }
where
init = do
subscribe UrlChanged Location.urlUpdates
...
view = ...
update = ...
#Subscription Source
newtype Subscription :: (Type -> Type) -> Type -> Type
newtype Subscription m a
Represents an external event, such as network, times, JS worker, etc.
The value wrapped in the newtype is a function that takes a message-dispatching callback, through which the subscription can send messages, and returns a "cleanup" action, which will be executed when it's time to unsubscribe from the subscription.
Constructors
Subscription (Dispatch a -> m (m Unit))
Instances
Functor (Subscription m)
#hush Source
hush :: forall m a. Subscription m (Maybe a) -> Subscription m a
Given a Maybe
-producing subscription, creates a new subscription that
filters out Nothing
values and fires only on receiving Just
.
#quasiBind Source
quasiBind :: forall m a b. (a -> Aff b) -> Subscription m a -> Subscription m b
This is almost a bind
, but not quite. The continuation function, instead
of returning another Subscription
(as a well behaved bind
would),
returns an Aff
. Another way of looking at it is as a version of map
,
but with the mapping function being effectful.
This is useful for building more complicated subscriptions on top of simpler ones. One can imagine a subscription that listens to a websocket, and upon getting a signal, fetches some data from the server, and this data becomes the "output" value of the subscription.
Q: Why not have a real bind
?
A: Since subscriptions are basically "infinite" streams of future values,
every incoming value from the outer subscription will have to create a
new subscription and keep it indefinitely, or until the outer
subscription is stopped. This means that the bind
will have to have a
local store of all new subscriptions created by the continuation
function, so that it can stop all of them when the outer subscription is
stopped. And this is just a memory leak. To be sure, there are ways
around that (basically reinventing reactive programming), but this use
case does not require that level of complexity.
#subscribe Source
subscribe :: forall m a msg. MonadEffect m => (a -> msg) -> Subscription m a -> Transition' m msg Unit
Given a subscription and a message constructor, this function "starts" the
subscription by embedding it in a state transition, which is then intended
to be part of a larger state transition - either a component's update
or
init
function.
Example:
data Message
= UrlChanged { path :: String, query :: String }
| ...
myComponent :: ComponentDef Message State
myComponent = { init, view, update }
where
init = do
subscribe UrlChanged Location.urlUpdates
...
view = ...
update = ...
#subscribe' Source
subscribe' :: forall m a. MonadEffect m => Subscription m a -> Transition' m a Unit
A version of subscribe
without the convenience mapping function. This
function is to support the implementation of lower-level primitives. In
most cases, in actual UI component code, subscribe
should be used.
#subscribeMaybe Source
subscribeMaybe :: forall m a msg. MonadEffect m => (a -> Maybe msg) -> Subscription m a -> Transition' m msg Unit
Similar to subscribe
, but instead of a message constructor, takes a
function returning Maybe Message
for issuing messages conditionally.
Example:
data Message
= UrlChanged String
| ...
myComponent :: ComponentDef Message State
myComponent = { init, view, update }
where
init = do
Location.urlUpdates # subscribeMaybe \{ path } ->
if path == "boring" then Nothing else Just $ UrlChanged path
...
view = ...
update = ...