Elmish.Component
- Package
- purescript-elmish
- Repository
- collegevine/purescript-elmish
#Transition Source
type Transition msg state = Transition' Aff msg state
#Transition' Source
data Transition' :: (Type -> Type) -> Type -> Type -> Type
data Transition' m msg state
A UI component state transition: wraps the new state value together with a (possibly empty) list of effects that the transition has caused (called "commands"), with each command possibly producing some new messages.
Instances of this type may be created either by using the smart constructor:
update :: State -> Message -> Transition Message State
update state m = transition state [someCommand]
or in monadic style (see comments on fork
for more on this):
update :: State -> Message -> Transition Message State
update state m = do
s1 <- Child1.update state.child1 Child1.SomeMessage # lmap Child1Msg
s2 <- Child2.modifyFoo state.child2 # lmap Child2Msg
fork someEffect
pure state { child1 = s1, child2 = s2 }
or, for simple sub-component delegation, the BiFunctor
instance may be
used:
update :: State -> Message -> Transition Message State
update state (ChildMsg m) =
Child.update state.child m
# bimap ChildMsg (state { child = _ })
Constructors
Transition state (Array (Command m msg))
Instances
(Functor m) => Bifunctor (Transition' m)
Functor (Transition' m msg)
Apply (Transition' m msg)
Applicative (Transition' m msg)
Bind (Transition' m msg)
Monad (Transition' m msg)
#Command Source
type Command :: (Type -> Type) -> Type -> Type
type Command m msg = { dispatch :: Dispatch msg, onStop :: m Unit -> Effect Unit } -> m Unit
An effect that is launched as a result of a component state transition.
It's a function that takes a callback, which allows it to produce (aka
"dispatch") messages, as well as an onStop
function, which allows it to
install a handler to be executed whent the component is destroyed (aka
"unmounted").
See forks
for a more detailed explanation.
#ComponentDef Source
type ComponentDef msg state = ComponentDef' Aff msg state
A ComponentDef'
in which effects run in Aff
.
#ComponentDef' Source
type ComponentDef' :: (Type -> Type) -> Type -> Type -> Type
type ComponentDef' m msg state = { init :: Transition' m msg state, update :: state -> msg -> Transition' m msg state, view :: state -> Dispatch msg -> ReactElement }
Definition of a component according to The Elm Architecture. Consists of
three functions - init
, view
, update
, - that together describe the
lifecycle of a component.
Type parameters:
m
- a monad in which the effects produced byupdate
andinit
functions run.msg
- component's message.state
- component's state.
#ComponentReturnCallback Source
type ComponentReturnCallback :: (Type -> Type) -> Type -> Type
type ComponentReturnCallback m a = forall state msg. ComponentDef' m msg state -> a
A callback used to return multiple components of different types. See below for a more detailed explanation.
This callback is handy in situations where a function must return different
components (with different state
and message
types) depending on
parameters. The prime example of such situation is routing.
Because most routes are served by different UI components, with different
state
and message
type parameters, the instantiating functions cannot
have the naive signature route -> component
: they need to "return"
differently-typed results depending on the route. In order to make that
happen, these functions instead take a polymorphic callback, to which they
pass the UI component. This type alias is the type of such callback: it
takes a polymorphically-typed UI component and returns "some value", a la
continuation-passing style.
Even though this type is rather trivial, it is included in the library for the purpose of attaching this documentation to it.
#transition Source
transition :: forall m state msg. Bind m => MonadEffect m => state -> Array (m msg) -> Transition' m msg state
Smart constructor for the Transition'
type. See comments there. This
function takes the new (i.e. updated) state and an array of commands - i.e.
effects producing messages - and constructs a Transition'
out of them
#fork Source
fork :: forall m message. MonadEffect m => m message -> Transition' m message Unit
Creates a Transition'
that contains the given command (i.e. a
message-producing effect). This is intended to be used for "accumulating"
effects while constructing a transition in imperative-ish style. When used
as an action inside a do
block, this function will have the effect of
"adding the command to the list" to be executed. The name fork
reflects
the fact that the given effect will be executed asynchronously, after the
update
function returns.
In more precise terms, the following:
trs :: Transition' m Message State
trs = do
fork f
fork g
pure s
Is equivalent to this:
trs :: Transition' m Message State
trs = transition s [f, g]
At first glance it may seem that it's shorter to just call the transition
smart constructor, but monadic style comes in handy for composing the
update out of smaller pieces. Here's a more full example:
data Message = ButtonClicked | OnNewItem String
update :: State -> Message -> Transition Message State
update state ButtonClick = do
fork $ insertItem "new list"
incButtonClickCount state
update state (OnNewItem str) =
...
insertItem :: Aff Message
insertItem name = do
delay $ Milliseconds 1000.0
pure $ OnNewItem name
incButtonClickCount :: Transition Message State
incButtonClickCount state = do
forkVoid $ trackingEvent "Button click"
pure $ state { buttonsClicked = state.buttonsClicked + 1 }
#forks Source
forks :: forall m message. Command m message -> Transition' m message Unit
Similar to fork
(see comments there for detailed explanation), but the
parameter is a function that takes dispatch
- a message-dispatching
callback, as well as onStop
- a way to be notified when the component is
destroyed (aka "unmounted"). This structure allows the command to produce
zero or multiple messages, unlike fork
, whose callback has to produce
exactly one, as well as stop listening or free resources etc. when the
component is unmounted.
NOTE: the onStop
callback is not recommended for direct use, use the
subscriptions API in Elmish.Subscription
instead.
Example:
update :: State -> Message -> Transition Message State
update state msg = do
forks countTo10
forks listenToUrl
pure state
countTo10 :: Command Aff Message
countTo10 { dispatch } =
for_ (1..10) \n ->
delay $ Milliseconds 1000.0
dispatch $ Count n
listenToUrl :: Command Aff Message
listenToUrl { dispatch, onStop } =
listener <-
window >>= addEventListener "popstate" do
newUrl <- window >>= location >>= href
dispatch $ UrlChanged newUrl
onStop $
window >>= removeEventListener listener
#forkVoid Source
forkVoid :: forall m message. m Unit -> Transition' m message Unit
Similar to fork
(see comments there for detailed explanation), but the
effect doesn't produce any messages, it's a fire-and-forget sort of effect.
#forkMaybe Source
forkMaybe :: forall m message. MonadEffect m => m (Maybe message) -> Transition' m message Unit
Similar to fork
(see comments there for detailed explanation), but the
effect may or may not produce a message, as modeled by returning Maybe
.
#withTrace Source
withTrace :: forall m msg state. DebugWarning => ComponentDef' m msg state -> ComponentDef' m msg state
Wraps the given component, intercepts its update cycle, and traces (i.e. prints to dev console) every command and every state value (as JSON objects), plus timing of renders and state transitions.
#nat Source
nat :: forall m n msg state. (m ~> n) -> ComponentDef' m msg state -> ComponentDef' n msg state
Monad transformation applied to ComponentDef'
#construct Source
construct :: forall msg state. ComponentDef msg state -> Effect ReactElement
Given a ComponentDef'
, binds that def to a freshly created React class,
instantiates that class, and returns a rendering function.
Unlike wrapWithLocalState
, this function uses the bullet-proof strategy
of storing the component state in a dedicated mutable cell, but that
happens at the expense of being effectful.
#wrapWithLocalState Source
wrapWithLocalState :: forall msg state args. ComponentName -> (args -> ComponentDef msg state) -> args -> ReactElement
Creates a React component that can be bound to a varying ComponentDef'
,
returns a function that performs the binding.
Note 1: this function accepts an Aff
-based ComponentDef'
, it cannot
take polymorphic or custom monad. The superficial reason for this is that
this function is intended to be used at top-level (see explanation below),
where context for a custom monad is not available. A deeper reason is that
this function creates a self-contained React component, and it is precisely
because it is self-contained that it cannot be seamlessly included in an
outer monadic computation.
This limitation forces such truly "reusable" components to be written in
terms of Aff
rather than a custom monad, which is actually a good thing.
However, if it turns out that this component really needs to be in a custom
monad, it is always possible to convert it to Aff
via the nat
function.
Note 2: in order to accomplish this, such aggregated component will store
its state using the React facilities - i.e. via this.setState
and
this.state
. While this is appropriate for most cases, it actually has
proven to be fragile in some specific circumstances (e.g. multiple events
occurring within the same JS synchronous frame), so it is not recommended
to use this mechanism for complex components or the top-level program.
#ComponentName Source
newtype ComponentName
A unique name for a component created via wrapWithLocalState
. These names
don't technically need to be completely unique, but they do need to be
unique enough so that two different wrapWithLocalState
-created components
that happen to have the same name never replace each other in the DOM. For
this reason, it is recommended to actually make sure these names are
unique, for example by appending a GUID to them. Read on for a more
detailed explanation.
React uses referential equality to decide whether to create a new instance of a component (and thus reset its local state) or keep the existing instance. This means that, on one hand, we cannot use the same React class for every instantiation, because this may create conflicts, where one Elmish component replaces another in the DOM, but they look like the same component to React, which makes it reuse state, which leads to breaking type safety. On the other hand, we cannot create a fresh class on every render, because then React will see it as a new component every time, and will reset its state every time.
This means that we need some way of figuring out whether it needs to be
logically the "same" component or "different", but there is no way to get that
"for free" (same way React gets it for free from referential equality) due
to PureScript's purity. Therefore, the only reliable way is to ask the
programmer, which is accomplished by requiring a ComponentName
, which
serves as a key.