Module

Halogen.Hooks

Package
purescript-halogen-hooks
Repository
thomashoneyman/purescript-halogen-hooks

This module implements the entire Halogen Hooks library. It's implemented as a monolithic module so that only types and functions meant for users are exported.

#useState Source

useState :: forall m state. state -> Hook m (UseState state) (state /\ (StateId state))

A Hook providing an independent state and a unique identifier usable with the state functions get, put, modify, and modify_ to update the state.

Hooks.do
  -- Create a new state with `useState`
  state /\ stateId <- Hooks.useState 0

  -- Perform state updates in `HookM`
  let
    update :: HookM m Unit
    update =
      Hooks.modify_ stateId \st -> st + 10

#UseState Source

data UseState :: Type -> Type -> Type

#useLifecycleEffect Source

useLifecycleEffect :: forall m. HookM m (Maybe (HookM m Unit)) -> Hook m UseEffect Unit

A Hook providing the ability to run an effect the first time the hook is run, which can return another effect to run the last time the hook is run. This is equivalent to component initializers and finalizers.

If you would like to run your effect after every render, see useTickEffect.

#useTickEffect Source

useTickEffect :: forall m. MemoValues -> HookM m (Maybe (HookM m Unit)) -> Hook m UseEffect Unit

A Hook providing the ability to run an effect after every render, which includes the first time the hook is run.

This Hook can be given an array of memo values as a performance optimization. If the provided array is empty, the effect will run on every render. If the array contains values, then the effect will only run on renders in which one or more of the memo values have changed.

To run an effect on every render:

Hooks.captures {} Hooks.useTickEffect do
  ...

To run an effect on the first render and when a particular value has changed:

Hooks.captures { memoA, memoB } Hooks.useTickEffect do
  ...

#UseEffect Source

data UseEffect :: Type -> Type

#useQuery Source

useQuery :: forall m query. QueryToken query -> (forall a. query a -> HookM m (Maybe a)) -> Hook m UseQuery Unit

A Hook providing the ability to receive and evaluate queries from a parent component. Only usable in components constructed with component, not in arbitrary hooks; the request/response nature of queries means they only make sense in the context of a component.

If this Hook is used multiple times in a single component definition, only the last use will take effect.

#UseQuery Source

data UseQuery :: Type -> Type

#useMemo Source

useMemo :: forall a m. MemoValues -> (Unit -> a) -> Hook m (UseMemo a) a

A Hook providing the ability to memoize a particular value.

When values are used in let bindings within the body of a Hook they are recomputed each time the Hook's body is evaluated (on every render). For values which are expensive to compute, you can either cache them in state (as you would with an ordinary Halogen component) or you can use useMemo.

All dependencies used to compute the memoized value should be provided to the captures or capturesWith function. Consider defining your useMemo Hook in a where clause to ensure you don't omit something by accident, which will lead to stale values.

-- before, computed on every render:
Hooks.do
  x /\ _ <- Hooks.useState 0
  y /\ _ <- Hooks.useState ""
  let expensiveValue = expensiveFunction x y

-- after, computed only if `x` or `y` have changed:
Hooks.do
  x /\ _ <- useState 0
  y /\ _ <- useState ""
  expensiveValue <- useExpensive x y
  ...
  where
  useExpensive deps@{ x, y } = Hooks.captures deps $ flip Hooks.useMemo \_ ->
    expensiveFunction x y

#UseMemo Source

data UseMemo :: Type -> Type -> Type

#useRef Source

useRef :: forall a m. a -> Hook m (UseRef a) (a /\ (Ref a))

A Hook providing the ability to use a mutable reference.

This Hook returns the value of the mutable reference at the time the Hook was run, and the reference itself which can be read at any time. The value of the reference can be used for rendering, but any effectful computations in HookM should read the value of the reference to guarantee an up-to-date value.

value /\ ref <- Hooks.useRef initialValue

-- Read and write the ref in effectful code
Hooks.captures {} Hooks.useTickEffect do
  current <- liftEffect $ Ref.read ref
  -- ... use the current value

-- Use the last-read value directly in render code
Hooks.pure $ HH.text (show value)

#UseRef Source

data UseRef :: Type -> Type -> Type

#captures Source

captures :: forall a memos. Eq (Record memos) => Record memos -> (MemoValues -> a) -> a

Used to improve performance for hooks which may be expensive to run on many renders (like useTickEffect and useMemo). Uses a value equality check to verify values have changed before re-running a function.

Some values may be expensive to check for value equality. You can optimize this by only checking a sub-part of your captured values using capturesWith

#capturesWith Source

capturesWith :: forall a memos. (Record memos -> Record memos -> Boolean) -> Record memos -> (MemoValues -> a) -> a

Like captures, but without an Eq constraint. Use when you only want to check part of a captured value for equality or when your captured values don't have Eq instances.

This function can recreate the usual captures:

Hooks.captures { memoA, memoB } == Hooks.capturesWith eq { memoA, memoB }

You can also choose to improve performance by testing only a sub-part of your memoized values. Remember that this equality check is used to decide whether to re-run your effect or function, so make sure to test everything in your captures list.

let
  customEq memoA memoB =
    memoA.user.id == memoB.user.id && memoA.data == memoB.data

Hooks.capturesWith customEq { user, data }

#wrap Source

wrap :: forall a m wrappedHooks internalHooks hooks. Newtype wrappedHooks internalHooks => Hooked m hooks internalHooks a -> Hooked m hooks wrappedHooks a

Hide a stack of hooks behind a newtype to improve error messages and ensure internal types like state are not leaked outside the module where the Hook is defined.

We recommend using this for any custom hooks you define.

newtype MyHook hooks = MyHook (UseState Int hooks)

derive instance newtypeMyHook :: Newtype (MyHook hooks) _

useMyHook :: forall ps o m. Hook ps o m MyHook Int
useMyHook = Hooks.wrap Hooks.do
  ... -- hook definition goes here

#bind Source

bind :: forall m z y x b a. IxBind m => m x y a -> (a -> m y z b) -> m x z b

Exported for use with qualified-do syntax

#discard Source

discard :: forall m z y x b a. IxBind m => m x y a -> (a -> m y z b) -> m x z b

Exported for use with qualified-do syntax

#pure Source

pure :: forall m x a. IxApplicative m => a -> m x x a

Exported for use with qualified-do syntax

Re-exports from Halogen.Hooks.Component

#memoComponent Source

memoComponent :: forall m o s i q hooks. (i -> i -> Boolean) -> (ComponentTokens q s o -> i -> Hooked m Unit hooks (ComponentHTML (HookM m Unit) s m)) -> Component HTML q i o m

A version of component which allows you to decide whether or not to send new input to the hook function based on an equality predicate. Halogen components send input to children on each render, which can cause a performance issue in some cases.

myComponent :: forall q o m. H.Component q Int o m
myComponent = Hooks.memoComponent eq \tokens input -> Hooks.do
  -- This hook implementation will not run when it receives new input
  -- unless the `Int` has changed.

Some input data may be more expensive to compute equality for than to simply send input again. In these cases you may want to write a more sophisticated equality function -- for example, only checking by a unique ID.

type User = { uuid :: Int, info :: HugeObject }

eqUser :: User -> User -> Boolean
eqUser userA userB = userA.uuid == userB.uuid

myComponent :: forall q o m. H.Component q User o m
myComponent = Hooks.memoComponent eqUser \_ input -> Hooks.do
  -- This hook implementation will not run when it receives new input
  -- unless the `User`'s id has changed.

#component Source

component :: forall m o ps i q hooks. (ComponentTokens q ps o -> i -> Hooked m Unit hooks (ComponentHTML (HookM m Unit) ps m)) -> Component HTML q i o m

Produces a Halogen component from a Hook which returns ComponentHTML. If you need to control whether Hooks evaluate when new input is received, see memoComponent.

Tokens are provided which enable access to component-only features like queries, output messages, and child slots, which don't make sense in a pure Hook context.

myComponent :: forall q i o m. H.Component q i o m
myComponent = Hooks.component \tokens input -> Hooks.do
  ... hook implementation

If you don't need to use tokens or input, you can use underscores to throw away those arguments.

myComponent :: forall q i o m. H.Component q i o m
myComponent = Hooks.component \_ _ -> Hooks.do
  ... hook implementation

If you are using tokens provided by the component function, you will have better type inference if you annotate the token type:

type Tokens = ComponentTokens MyQuery MySlots MyOutput

myComponent :: forall i m. H.Component MyQuery i MyOutput m
myComponent = Hooks.component \(tokens :: Tokens) _ -> Hooks.do
  ... hook implementation

Re-exports from Halogen.Hooks.Hook

#Hooked Source

newtype Hooked m pre post a

A largely internal type which underlies the Hook type. Used when the first type variable of the indexed monad, hooks, cannot be hidden.

Constructors

Instances

#Hook Source

type Hook m (newHook :: Type -> Type) a = forall hooks. Hooked m hooks (newHook hooks) a

A function which has access to primitive and custom hooks like UseState, UseEffect, UseRef, and UseMemo. Hook functions can be used to implement reusable, stateful logic and to implement Halogen components.

Functions of this type should be constructed using the Hooks API exposed by Halogen.Hooks.

Re-exports from Halogen.Hooks.HookM

#HookM Source

newtype HookM m a

The Hook effect monad, used to write effectful code in Hooks functions. This monad is fully compatible with HalogenM, meaning all functionality available for HalogenM is available in HookM.

Constructors

Instances

#HookF Source

data HookF m a

A DSL compatible with HalogenM which is used to write effectful code for Hooks.

Constructors

Instances

#HookAp Source

newtype HookAp m a

An applicative-only version of HookM to allow for parallel evaluation.

Constructors

Instances

#unsubscribe Source

unsubscribe :: forall m. SubscriptionId -> HookM m Unit

Unsubscribes a component from an EventSource. If the subscription associated with the ID has already ended this will have no effect.

#subscribe' Source

subscribe' :: forall m. (SubscriptionId -> EventSource m (HookM m Unit)) -> HookM m Unit

An alternative to subscribe, intended for subscriptions that unsubscribe themselves. Instead of returning the SubscriptionId from subscribe', it is passed into an EventSource constructor. This allows emitted queries to include the SubscriptionId, rather than storing it in the state of the component.

When a component is disposed of any active subscriptions will automatically be stopped and no further subscriptions will be possible during finalization.

#subscribe Source

subscribe :: forall m. EventSource m (HookM m Unit) -> HookM m SubscriptionId

Subscribes a component to an EventSource. When a component is disposed of any active subscriptions will automatically be stopped and no further subscriptions will be possible during finalization.

#raise Source

raise :: forall m o. OutputToken o -> o -> HookM m Unit

Raise an output message for the component. Requires a token carrying the output type of the component, which is provided by the Hooks.component function.

#queryAll Source

queryAll :: forall _1 a slot o' query ps label m. Cons label (Slot query o' slot) _1 ps => IsSymbol label => Ord slot => SlotToken ps -> SProxy label -> query a -> HookM m (Map slot a)

Send a query to all children of a component at the specified slot. Requires a token carrying the slot type of the component, which is provided by the Hooks.component function.

#query Source

query :: forall _1 a slot o' query ps label m. Cons label (Slot query o' slot) _1 ps => IsSymbol label => Ord slot => SlotToken ps -> SProxy label -> slot -> query a -> HookM m (Maybe a)

Send a query to a child of a component at the specified slot. Requires a token carrying the slot type of the component, which is provided by the Hooks.component function.

#put Source

put :: forall m state. StateId state -> state -> HookM m Unit

Overwrite a piece of state using an identifier received from the useState hook.

_ /\ countId :: StateId Int <- Hooks.useState 0

let
  onClick = do
    Hooks.put countId 10

#modify_ Source

modify_ :: forall m state. StateId state -> (state -> state) -> HookM m Unit

Modify a piece of state using an identifier received from the useState hook.

_ /\ countId :: StateId Int <- Hooks.useState 0

let
  onClick = do
    Hooks.modify_ countId (_ + 10)

#modify Source

modify :: forall m state. StateId state -> (state -> state) -> HookM m state

Modify a piece of state using an identifier received from the useState hook, returning the new state.

_ /\ countId :: StateId Int <- Hooks.useState 0

let
  onClick = do
    count :: Int <- Hooks.modify countId (_ + 10)
    ...

#kill Source

kill :: forall m. ForkId -> HookM m Unit

Kills a forked process if it is still running. Attempting to kill a forked process that has already ended will have no effect.

#getRef Source

getRef :: forall m. RefLabel -> HookM m (Maybe Element)

Retrieves an Element value that is associated with a Ref in the rendered o of a component. If there is no currently rendered value for the requested ref this will return Nothing.

#getHTMLElementRef Source

getHTMLElementRef :: forall m. RefLabel -> HookM m (Maybe HTMLElement)

Retrieves a HTMLElement value that is associated with a Ref in the rendered o of a component. If there is no currently rendered value (or it is not an HTMLElement) for the request will return Nothing.

#get Source

get :: forall m state. StateId state -> HookM m state

Get a piece of state using an identifier received from the useState hook.

_ /\ countId :: StateId Int <- Hooks.useState 0

let
  onClick = do
    count :: Int <- Hooks.get countId
    ...

#fork Source

fork :: forall m. HookM m Unit -> HookM m ForkId

Starts a HalogenM process running independent from the current eval "thread".

A commonly use case for fork is in component initializers where some async action is started. Normally all interaction with the component will be blocked until the initializer completes, but if the async action is forked instead, the initializer can complete synchronously while the async action continues.

Some care needs to be taken when using a fork that can modify the component state, as it's easy for the forked process to "clobber" the state (overwrite some or all of it with an old value) by mistake.

When a component is disposed of any active forks will automatically be killed. New forks can be started during finalization but there will be no means of killing them.

Re-exports from Halogen.Hooks.Types

#StateId Source

newtype StateId state

A unique identifier for a state produced by useState, which can be passed to the state functions get, put, modify, and modify_ to get or modify the state.

This token should NOT be modified.

state /\ stateId <- useState 0

let
  handler = Hooks.modify_ stateId (_ + 10)

Constructors

#SlotToken Source

data SlotToken (slots :: Row Type)

A token which carries the type of child slots supported by the component which is executing a Hook. Child slots are specific to the parent-child component relationship, and so they are not tracked in Hook types.

This token is provided by the component function.

#QueryToken Source

data QueryToken (a :: Type -> Type)

A token which carries the type of queries supported by the component which is executing a Hook. Queries are specific to the parent-child component relationship, and so they are not tracked in Hook types.

This token is provided by the component function.

#OutputToken Source

data OutputToken output

A token which carries the type of outputs supported by the component which is executing a Hook. Output messages slots are specific to the parent-child component relationship, and so they are not tracked in Hook types.

This token is provided by the component function.

#MemoValues Source

data MemoValues

An opaque type which signifies that a set of dependencies have been captured and can be used by Hooks like UseMemo and UseEffect.

This type is provided by the captures and capturesWith functions.

#ComponentTokens Source

type ComponentTokens q ps o = { outputToken :: OutputToken o, queryToken :: QueryToken q, slotToken :: SlotToken ps }

The set of tokens enabling queries, child slots, and output messages when running a Hook as a component. This set of tokens is provided by the Hooks.component function.

Hooks do not have a notion of parent / child relationships, and Halogen features like queries and outputs don't make sense in the context of Hooks. These tokens enable those features for Hooks which are being turned into components, while ensuring Hooks which are being nested are not able to access those features.