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 state m. 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
#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
...
#useQuery Source
useQuery :: forall query m. 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.
#useMemo Source
useMemo :: forall m a. 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
#useRef Source
useRef :: forall m a. 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)
#captures Source
captures :: forall memos a. 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 memos a. (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 h h' m a. HookNewtype h' h => Hook m h a -> Hook m h' a
Make a stack of hooks opaque 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.
foreign import data MyHook :: HookType
instance newtypeMyHook :: HookNewtype MyHook (UseState Int <> Pure)
useMyHook :: forall m. Hook m MyHook Int
useMyHook = Hooks.wrap Hooks.do
... -- hook definition goes here
Re-exports from Halogen.Hooks.Component
#memoComponent Source
memoComponent :: forall hooks q i s o m. (i -> i -> Boolean) -> (ComponentTokens q s o -> i -> Hook m hooks (ComponentHTML (HookM m Unit) s m)) -> Component 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 hooks q i s o m. (ComponentTokens q s o -> i -> Hook m hooks (ComponentHTML (HookM m Unit) s m)) -> Component 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 = Hooks.ComponentTokens MyQuery MySlots MyOutput
myComponent :: forall i m. H.Component MyQuery i MyOutput m
myComponent = Hooks.component \(tokens :: Tokens) _ -> Hooks.do
... hook implementation
Use type variables to substitue unused token types:
type Tokens s o = Hooks.ComponentTokens MyQuery s o
myComponent :: forall i o m. H.Component MyQuery i o m myComponent = Hooks.component (tokens :: Tokens _ o) _ -> Hooks.do ... hook implementation
Re-exports from Halogen.Hooks.Hook
#HookAppend Source
data HookAppend :: HookType -> HookType -> HookType
data HookAppend t0 t1
A type for listing several Hook types in order. Typically this is used via
the operator <>
.
``purs
import Halogen.Hooks (type (<>))
type UseStateEffect = UseState Int <> UseEffect <> Pure
-- using to the type UseStateEffect = HookAppend (UseState Int) (HookAppend UseEffect Nil)
#Hook Source
newtype Hook :: (Type -> Type) -> HookType -> Type -> Type
newtype Hook m h 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
.
Instances
#HookNewtype Source
class HookNewtype :: HookType -> HookType -> Constraint
class HookNewtype (a :: HookType) (b :: HookType) | a -> b
A class for asserting that one HookType
can be "unwrapped" to produce
the other. This class is used to turn a list of Hooks into a new opaque
Hook in conjunction with wrap
:
foreign import data UseX :: HookType
instance newtypeUseX :: HookNewtype UseX (UseState Int <> UseEffect <> Pure)
useX :: forall m. Hook m UseX Int
useX = Hooks.wrap Hooks.do
-- ... use useState, useEffect in the implementation
#type (<>) Source
Operator alias for Halogen.Hooks.Hook.HookAppend (right-associative / precedence 1)
HookAppend
as an infix operator
Re-exports from Halogen.Hooks.HookM
#HookM Source
newtype HookM :: (Type -> Type) -> Type -> Type
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
Functor (HookM m)
Apply (HookM m)
Applicative (HookM m)
Bind (HookM m)
Monad (HookM m)
(Semigroup a) => Semigroup (HookM m a)
(Monoid a) => Monoid (HookM m a)
(MonadEffect m) => MonadEffect (HookM m)
(MonadAff m) => MonadAff (HookM m)
MonadTrans HookM
MonadRec (HookM m)
(MonadAsk r m) => MonadAsk r (HookM m)
(MonadTell w m) => MonadTell w (HookM m)
(MonadThrow e m) => MonadThrow e (HookM m)
Parallel (HookAp m) (HookM m)
#HookF Source
data HookF :: (Type -> Type) -> Type -> Type
data HookF m a
A DSL compatible with HalogenM which is used to write effectful code for Hooks.
Constructors
Modify (StateId StateValue) (StateValue -> StateValue) (StateValue -> a)
Subscribe (SubscriptionId -> Emitter (HookM m Unit)) (SubscriptionId -> a)
Unsubscribe SubscriptionId a
Lift (m a)
ChildQuery (ChildQueryBox SlotType a)
Raise OutputValue a
Par (HookAp m a)
Fork (HookM m Unit) (ForkId -> a)
Kill ForkId a
GetRef RefLabel (Maybe Element -> a)
Instances
#unsubscribe Source
unsubscribe :: forall m. SubscriptionId -> HookM m Unit
Unsubscribes a component from an Emitter
. If the subscription
associated with the ID has already ended this will have no effect.
#tell Source
tell :: forall m label ps query o' slot _1. Cons label (Slot query o' slot) _1 ps => IsSymbol label => Ord slot => SlotToken ps -> Proxy label -> slot -> Tell query -> HookM m Unit
Send a tell-request 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.
#subscribe' Source
subscribe' :: forall m. (SubscriptionId -> Emitter (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 Emitter
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.
#request Source
request :: forall m label ps query o' slot a _1. Cons label (Slot query o' slot) _1 ps => IsSymbol label => Ord slot => SlotToken ps -> Proxy label -> slot -> Request query a -> HookM m (Maybe a)
Send a query-request 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.
#raise Source
raise :: forall o m. 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 m label ps query o' slot a _1. Cons label (Slot query o' slot) _1 ps => IsSymbol label => Ord slot => SlotToken ps -> Proxy 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 m label ps query o' slot a _1. Cons label (Slot query o' slot) _1 ps => IsSymbol label => Ord slot => SlotToken ps -> Proxy 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.
#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
.
#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
fork
ed 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)
#SlotToken Source
data SlotToken :: Row Type -> Type
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 :: (Type -> Type) -> Type
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.
#HookType Source
data HookType
The kind of types used in Hooks; primitive Hooks already have this kind, and Hooks of your own should be foreign imported data types that are also types of this kind:
foreign import data UseX :: Hooks.HookType
#ComponentTokens Source
type ComponentTokens :: (Type -> Type) -> Row Type -> Type -> Type
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.