Module

Elmish.Hooks.Type

Package
purescript-elmish-hooks
Repository
collegevine/purescript-elmish-hooks

#ComposedHookTypes Source

class ComposedHookTypes :: HookType' -> HookType' -> HookType' -> Constraintclass ComposedHookTypes (left :: HookType') (right :: HookType') (result :: HookType') | left right -> result

This class represents the type level function for composing HookType's, with instances for appending the identity and arbitrary HookType's.

Instances

#AppendHookType Source

type AppendHookType :: (HookType' -> HookType') -> HookType' -> HookType'type AppendHookType (h :: HookType) t = h t

A type which allows appending HookTypes via type application.

Instances

#Hook Source

type Hook :: (HookType' -> HookType') -> Type -> Typetype Hook (t :: HookType) a = Hook' (t Pure) a

A Convenient wrapper which applies Pure to the given hook type.

#Hook' Source

data Hook' :: HookType' -> Type -> Typedata Hook' (t :: HookType') 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 …

Finally, to make sure we don’t use two hooks with different state-types in a conditional (which could cause unexpected issues for React) we have a HookType' parameter, which accumulates when we call bind. For this we need to use qualified do notation:

withHooks Hooks.do
  foo /\ setFoo <- useState ""
  Hooks.pure …

Instances

#HookType' Source

data HookType' :: Type

Represents the type of a hook and is used to ensure hooks are safe. For example, the following will not compile because we track HookType:

withHooks Hooks.do
  if someCondition then Hooks.do
    x <- useState ""
    _ <- useState 0
    Hooks.pure x
  else Hooks.do
    _ <- useState 0
    useState ""

because the first block has a HookType of UseState String <> UseState Int and the second is UseState Int <> UseState String. The same hooks need to be used in the same order for the hook types to match.

#Pure Source

data Pure :: HookType'

The HookType of pure — the identity of the HookType monoid.

Instances

#type (<>) Source

Operator alias for Elmish.Hooks.Type.AppendHookType (right-associative / precedence 6)

#bind Source

bind :: forall ta tb tr a b. ComposedHookTypes ta tb tr => Hook' ta a -> (a -> Hook' tb b) -> Hook' tr b

#discard Source

discard :: forall ta tb tr a b. Discard a => ComposedHookTypes ta tb tr => Hook' ta a -> (a -> Hook' tb b) -> Hook' tr b

#mkHook Source

mkHook :: forall msg state t a. ComponentName -> ((a -> ReactElement) -> ComponentDef msg state) -> Hook' t a

Given a ComponentName and a function to create a ComponentDef (from a render function a -> ReactElement), mkHook creates a Hook a. When creating a hook with mkHook, you’ll need to create a HookType by foreign importing it.

As an example of how to use mkHook, useEffect uses it like so:

foreign import data UseEffect :: Type -> HookType

useEffect :: Aff Unit -> Hook (UseEffect Unit) Unit
useEffect init =
  mkHook (ComponentName "UseEffect") \render ->
    { init: forkVoid init
    , update: \_ msg -> absurd msg
    , view: \_ _ -> render unit
    }

#pure Source

pure :: forall a. a -> Hook' Pure a

#withHook Source

withHook :: forall t a. Hook' t a -> (a -> ReactElement) -> ReactElement

When there is only one hook, it might make more sense to invoke it with continuation-passing style. This helper makes that easier, accepting a render callback.

Intended to be used via the ==> operator:

view :: ReactElement
view = useState "" ==> \(name /\ setName) ->
  H.input_ "" { value: name, onChange: setName <?| eventTargetValue }

#withHookCurried Source

withHookCurried :: forall t a b. Hook' t (a /\ b) -> (a -> b -> ReactElement) -> ReactElement

Given a Hook (a /\ b), this allows invoking it with a curried render callback.

Intended to be used via the =/> operator:

view :: ReactElement
view = useState "" =/> \name setName ->
  H.input_ "" { value: name, onChange: setName <?| eventTargetValue }

#withHooks Source

withHooks :: forall t. Hook' t 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 Hooks.do
  name /\ setName <- useState ""
  Hooks.pure $ H.input_ "" { value: name, onChange: setName <?| eventTargetValue }

#(=/>) Source

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

#(==>) Source

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