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 aThe 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 -> ReactElementUnwraps 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 } -> ComponentNameGenerates a ComponentName to be passed to mkHook.
#mkHook Source
mkHook :: forall msg state a. ComponentName -> ((a -> ReactElement) -> ComponentDef msg state) -> Hook aGiven 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 UnitThe 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
]