Module

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" }

#(==>) Source

Operator alias for Elmish.Hooks.Type.withHook (left-associative / precedence 1)

#(=/>) Source

Operator alias for Elmish.Hooks.Type.withHookCurried (left-associative / precedence 1)

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
    ]