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 UnitA 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 UnitA 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 UnitA 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) aA 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) -> aUsed 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) -> aLike 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' aMake 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 mA 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 mProduces 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 -> HookTypedata 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 -> Typenewtype 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 -> Constraintclass 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 -> Typenewtype 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 HookMMonadRec (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 -> Typedata 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 aLift (m a)ChildQuery (ChildQueryBox SlotType a)Raise OutputValue aPar (HookAp m a)Fork (HookM m Unit) (ForkId -> a)Kill ForkId aGetRef RefLabel (Maybe Element -> a)
Instances
#unsubscribe Source
unsubscribe :: forall m. SubscriptionId -> HookM m UnitUnsubscribes 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 UnitSend 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 UnitAn 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 UnitRaise 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 ForkIdStarts 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 stateA 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 -> Typedata 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) -> Typedata 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 outputA 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 MemoValuesAn 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 HookTypeThe 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 -> Typetype 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.