Module

Elmish.Component

Package
purescript-elmish
Repository
collegevine/purescript-elmish

#Transition Source

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, with each effect ultimately producing a new message.

Constructors

Instances

#ComponentDef Source

type ComponentDef m msg state = { init :: Transition m msg state, update :: state -> msg -> Transition m msg state, view :: state -> DispatchMsgFn 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 m a = forall msg state. ComponentDef m msg state -> a

A callback used to return multiple components of different types. See below for more a 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.

#mapCmds Source

mapCmds :: forall innerMsg msg m. Functor m => (msg -> innerMsg) -> Array (m msg) -> Array (m innerMsg)

#(<$$>) Source

Operator alias for Elmish.Component.mapCmds (non-associative / precedence 8)

A nested map - useful for mapping over commands in an array: first map maps over the array, second map maps over the monad m.

Example:

 let (Transition subS cmds) = SubComponent.update s.subComponent msg
 in Transition (s { subComponent = subS }) (SubComponentMsg <$$> cmds)

#pureUpdate Source

pureUpdate :: forall state msg m. state -> Transition m msg state

Creates a Transition without any commands. This function will be deprecated soon in favor of pure.

#withTrace Source

withTrace :: forall state msg m. 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).

#nat Source

nat :: forall state msg n m. (forall a. m a -> n a) -> ComponentDef m msg state -> ComponentDef n msg state

Monad transformation applied to ComponentDef

#construct Source

construct :: forall state msg. ComponentDef Aff msg state -> Effect (DispatchMsgFn Unit -> ReactElement)

Given a ComponentDef, binds that def to a freshly created React class, instantiates that class, and returns a rendering function. Note that the return type of this function is almost the same as that of ComponentDef::view - except for state. This is not a coincidence: it is done this way on purpose, so that the result of this call can be used to construct another ComponentDef.

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 args state msg. ComponentName -> (args -> ComponentDef Aff msg state) -> DispatchMsgFn Unit -> 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.

Note 3: this function has to be called exactly once, at top-level. It cannot be called in the parent's view function on every render. Every time it is called it creates a new React class, so if it was called on every render, the resulting JSX DOM will contain a new component every time, which will make React remount that component and destroy its local state. See explanation on withCachedComponent for more.

#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 chaos. 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 "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