Elmish.Hooks
- Package
- purescript-elmish-hooks
- Repository
- collegevine/purescript-elmish-hooks
A React hook-like library for Elmish. Uses a continuation monad to
encapsulate state or effects. Similarly to React, hooks should be used at
the top level of a withHooks do …
block, not inside conditionals.
todos :: ReactElement
todos = withHooks do
todos /\ setTodos <- useState []
useEffect do
todos <- API.fetchTodos
liftEffect $ setTodos todos
pure $ H.fragment $ todoView <$> todos
Re-exports from Elmish.Hooks.Type
#Hook Source
newtype Hook a
The type of a hook, e.g. the result of calling useState
. It turns out
that hooks can be modeled as a continuation, where the callback function
returns a new component (created with wrapWithLocalState
) given the
encapsulated value. E.g., in the case of useState
, you can think of it as
accepting a callback function, which gets passed the current state and a
setter for the current state:
useState "" \(foo /\ setFoo) -> …
Modeling it as a continuation allows us to make it a monad and write in do-notation, which looks a lot like the React hooks syntax:
withHooks do
foo /\ setFoo <- useState ""
pure …
Instances
#withHooks Source
withHooks :: Hook ReactElement -> ReactElement
Unwraps a Hook ReactElement
, which is usually created by using one or
more hooks and then using pure
to encapsulate a ReactElement
. E.g.:
view :: ReactElement
view = withHooks do
name /\ setName <- useState ""
pure $ H.input_ "" { value: name, onChange: setName <?| eventTargetValue }
#uniqueNameFromCurrentCallStack Source
uniqueNameFromCurrentCallStack :: { prefix :: String, skipFrames :: Int } -> ComponentName
Generates a ComponentName
to be passed to mkHook
.
#mkHook Source
mkHook :: forall msg state a. ComponentName -> ((a -> ReactElement) -> ComponentDef msg state) -> Hook a
Given a ComponentName
and a function to create a ComponentDef
(from a
render function a -> ReactElement
), mkHook
creates a Hook a
. The name
can be anything, but it’s recommended to use
uniqueNameFromCurrentCallStack
to create a unique name based on where the
hook is called from in the stack trace. uniqueNameFromCurrentCallStack
accepts a skipFrames :: Int
argument to indicate how many frames back it
should look for the call site of the hook, as well as a prefix
argument
that will be the prefix of the resulting ComponentName
.
It’s also recommended to create the name in a where clause and make your hook a function accepting one argument. Even if your hook takes more than one argument, you can put the rest o the arguments in a lambda in the function body:
myHook x = \y z -> mkHook …
where
name = uniqueNameFromCurrentCallStack { skipFrames: 3, prefix: "MyHook" }
This ensures that the number of frames to skip is predictably 3. If
defining the function differently, a different number of frames can be
passed. uniqueNameFromCurrentCallStackTraced
can be used to help find the
correct number.
As an example of how to use mkHook
, useEffect
uses it like so:
useEffect :: Aff Unit -> Hook Unit
useEffect init =
mkHook name \render ->
{ init: forkVoid init
, update: \_ msg -> absurd msg
, view: \_ _ -> render unit
}
where
name = ComponentName $ genStableUUID { skipFrames: 3, prefix: "UseEffect" }
Re-exports from Elmish.Hooks.UseEffect
#useEffect Source
useEffect :: Aff Unit -> Hook Unit
The useEffect
hook takes an effect (Aff
) to run and runs it in the
init
of the resulting component. E.g.:
todos :: ReactElement
todos = withHooks do
todos /\ setTodos <- useState []
useEffect do
todos <- API.fetchTodos
liftEffect $ setTodos todos
pure $ H.fragment $ todoView <$> todos
Re-exports from Elmish.Hooks.UseState
#useState Source
useState :: forall state. state -> Hook (state /\ (Dispatch state))
The useState
hook takes an initial state and returns a Hook
encapsulating the current state and a setState
function. E.g.:
view :: ReactElement
view = withHooks do
visible /\ setVisible <- useState false
pure $
H.fragment
[ H.button_ "" { onClick: setVisible $ not visible } "Toggle visibility"
, if visible
then H.div "" "Content"
else H.empty
]