Module

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 -> Typedata 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

Instances

#Command Source

type Command :: (Type -> Type) -> Type -> Typetype 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 -> Typetype 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 by update and init functions run.
  • msg - component's message.
  • state - component's state.

#ComponentReturnCallback Source

type ComponentReturnCallback :: (Type -> Type) -> Type -> Typetype 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.

Constructors

Re-exports from Data.Bifunctor

#bimap Source

bimap :: forall f a b c d. Bifunctor f => (a -> b) -> (c -> d) -> f a c -> f b d

#rmap Source

rmap :: forall f a b c. Bifunctor f => (b -> c) -> f a b -> f a c

Map a function over the second type arguments of a Bifunctor.

#lmap Source

lmap :: forall f a b c. Bifunctor f => (a -> b) -> f a c -> f b c

Map a function over the first type argument of a Bifunctor.