Elmish
- Package
- purescript-elmish
- Repository
- collegevine/purescript-elmish
Re-exports from Elmish.Boot
#BootRecord Source
type BootRecord props = { hydrate :: String -> props -> Effect Unit, mount :: String -> props -> Effect Unit, renderToString :: props -> String }
Support for the most common case entry point - i.e. mounting an Elmish
component (i.e. ComponentDef'
structure) to an HTML DOM element with a
known ID, with support for server-side rendering.
The function boot
returns what we call BootRecord
- a record of three
functions:
mount
- takes HTML element ID and props¹, creates an instance of the component, and mounts it to the HTML element in questionhydrate
- same asmount
, but expects the HTML element to already contain pre-rendered HTML inside. See React docs for more on server-side rendering: https://reactjs.org/docs/react-dom.html#hydraterenderToString
- meant to be called on the server (e.g. by running the code under NodeJS) to perform the server-side render. Takes props¹ and returns aString
containing the resulting HTML.
The idea is that the PureScript code would export such BootRecord
for
consumption by bootstrap JavaScript code in the page and/or server-side
NodeJS code (which could be written in PureScript or not). For "plain
React" scenario, the JavaScript code in the page would just call mount
.
For "server-side rendering", the server would first call renderToString
and serve the HTML to the client, and then the client-side JavaScript code
would call hydrate
.
¹ "props" here is a parameter used to instantiate the component (see example below). It is recommended that this parameter is a JavaScript record (hence the name "props"), because it would likely need to be supplied by some bootstrap JavaScript code.
Example:
-- PureScript:
module Foo(bootRecord) where
type Props = { hello :: String, world :: Int }
component :: Props -> ComponentDef' Aff Message State
component = ...
bootRecord :: BootRecord Props
bootRecord = boot component
// Server-side JavaScript NodeJS code
const foo = require('output/Foo/index.js')
const fooHtml = foo.bootRecord.renderToString({ hello: "Hi!", world: 42 })
serveToClient("<html><body><div id='foo'>" + fooHtml + "</div></body></html>")
// Client-side HTML + JS:
<html>
<body>
<div id='foo'>
... server-side-rendered HTML goes here
</div>
</body>
<script src="foo_bundle.js" />
<script>
Foo.bootRecord.hydrate('foo', { hello: "Hi!", world: 42 })
</script>
</html>
#boot Source
boot :: forall msg state props. (props -> ComponentDef msg state) -> BootRecord props
Creates a boot record for the given component. See comments for BootRecord
.
Re-exports from Elmish.Component
#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)
#Transition Source
type Transition msg state = Transition' Aff msg state
#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.
#ComponentDef Source
type ComponentDef msg state = ComponentDef' Aff msg state
A ComponentDef'
in which effects run in Aff
.
#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.
#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
#nat Source
nat :: forall m n msg state. (m ~> n) -> ComponentDef' m msg state -> ComponentDef' n msg state
Monad transformation applied to ComponentDef'
#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
.
#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 }
#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.
Re-exports from Elmish.Dispatch
#handle Source
handle :: forall msg event f. Handle msg event f => Dispatch msg -> f -> EffectFn1 event Unit
A convenience function to make construction of event handlers with
arguments (i.e. EffectFn1
) a bit shorter. The first parameter is a
Dispatch
. The second parameter can be either a message or a function
from the event object to a message.
Expected usage for this function is in its operator form <|
textarea
{ value: state.text
, onChange: dispatch <| \e -> TextAreaChanged (E.textareaText e)
, onMouseDown: dispatch <| TextareaClicked
}
#handleMaybe Source
handleMaybe :: forall msg event f. HandleMaybe msg event f => Dispatch msg -> f -> EffectFn1 event Unit
A variant of handle
(aka <|
) that allows to dispatch a message or
not conditionally via returning a Maybe message
.
Expected usage for this function is in its operator form <?|
div
{ onMouseDown: dispatch <?| \(E.MouseEvent e) ->
if e.ctrlKey then Just ClickedWithControl else Nothing
}
Re-exports from Elmish.React
#ReactElement Source
data ReactElement
Instantiated subtree of React DOM. JSX syntax produces values of this type.
Instances
#ReactComponent Source
data ReactComponent t0
This type represents constructor of a React component with a particular
behavior. The type prameter is the record of props (in React lingo) that
this component expects. Such constructors can be "rendered" into
ReactElement
via createElement
.
#createElement' Source
createElement' :: forall props. ValidReactProps props => ReactComponent props -> props -> ReactElement
Variant of createElement
for creating an element without children.
#createElement Source
createElement :: forall props content. ValidReactProps props => ReactChildren content => ReactComponent props -> props -> content -> ReactElement
The PureScript import of the React’s createElement
function. Takes a
component constructor, a record of props, some children, and returns a
React DOM element.
To represent HTML data-
attributes, createElement
supports the
_data :: Object
prop.
Example
import Elmish.HTML as H
import Foreign.Object as FO
H.div
{ _data: FO.fromHomogenous { toggle: "buttons" } }
[...]
represents the <div data-toggle="buttons">
DOM element.
#callbackRef Source
callbackRef :: forall el. Maybe el -> (Maybe el -> Effect Unit) -> Ref el
Takes the current ref value and a callback function (el -> Effect Unit
)
and returns a Ref
. The current ref value is needed so that we can decide
whether the callback function should be run (by comparing the current ref
and the new one by reference). The callback function should add the el
parameter to some state. E.g.:
data Message = RefChanged (Maybe HTMLInputElement) | …
view :: State -> Dispatch Message -> ReactElement
view state dispatch =
H.input_ "" { ref: callbackRef state.inputElement (dispatch <<< RefChanged), … }
update :: State -> Message -> Transition Message State
update state = case _ of
RefChanged ref -> pure state { inputElement = ref }
…
Re-exports from Elmish.Subscription
#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 = ...
#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 = ...