Halogen.Hooks.Extra.Hooks
- Package
- purescript-halogen-hooks-extra
- Repository
- jordanmartinez/purescript-halogen-hooks-extra
Reexports all Hooks defined in this repository.
Re-exports from Halogen.Hooks.Extra.Hooks.UseDebouncer
#UseDebouncer Source
data UseDebouncer :: Type -> HookType
data UseDebouncer t0
#useDebouncer Source
useDebouncer :: forall m a. MonadAff m => Milliseconds -> (a -> HookM m Unit) -> Hook m (UseDebouncer a) (a -> HookM m Unit)
A hook that, once the given time period ends, will run an action using the last value written. Once the initial write occurs, a timer is set and begins counting down. If a new write occurs before that timer ends, the timer restarts. When the timer ends, the last value that was written will be passed into the handler.
Example Usage
The below example shows how to update the label with the value the user inputted after there have been 500ms of no user input.
myComponent = Hooks.component \_ -> Hooks.do
label /\ labelId <- useState ""
makeNewSearchFor <- useDebouncer (Milliseconds 500.0) \finalValue -> do
Hooks.put labelId finalValue
Hooks.pure
HH.div_
[ HH.h1_
[ HH.text $ "Label text is: " <> label ]
, HH.input
[ HP.onValueInput \str -> Just (makeNewSearchFor str) ]
]
Re-exports from Halogen.Hooks.Extra.Hooks.UseEvent
#useEvent Source
useEvent :: forall m a. MonadEffect m => Hook m (UseEvent m a) (UseEventApi m a)
Allows you to "push" events that occur inside a hook to a single handler outside of the hook. This allows the end user to use the full API returned by the hook when handling the event. Moreover, the end-user can set up resources on the first time the handler is run and unsubscribe when the finalizer is run.
For example...
-- let's say this is the end-user's Hook code
onEvent <- useEvent
-- Here, we'll inline the code for a hypothetical hook we found.
-- This could be a hook provided by a library or something.
{ foo } <- Hooks.do
-- somewhere in the hypothetical hook, an event occurs
onEvent.push "user clicked foo"
-- return the value of the hook provided by the library
pure { foo: "foo" }
Hooks.useLifecycleEffect do
unsubscribe <- onEvent.setCallback $ Just \setupSubscription str -> do
-- handle the event
Hooks.raise ("Event occurred: " <> str)
setupSubscription do
-- Then, set up some resources in this code block
-- that need to be cleaned up later
liftEffect $ log $ "Setting up resources."
pure do
-- now define the code that will run when we call
-- 'unsubscribe' later
liftEffect $ log $ "Cleaning up resources."
pure $ Just do
-- unsubscribe to clean up resources
unsubscribe
If you don't need to unsubscribe, just ignore the first argument
in the function passed to onEvent
:
state /\ stateId <- useState 0
Hooks.captures { state } Hooks.useTickEffect do
-- Notice two things here:
-- 1. we're ignoring the 'unsubscribeCallback' argument
-- by using the underscore (i.e. _).
-- 2. we're ignoring the returned 'unsubscribe' code by using `void`.
void $ onEvent \_ string -> do
-- handle the event
Hooks.raise ("Event occurred: " <> string)
pure Nothing -- no need to unsubscribe here
Beware Infinite Loops
If you use this hook, it's possible for you to create an infinite loop. This will occur if a handler runs code that causes another event to be emitted. Consider this workflow:
- Event A is emitted
- During A's handler, Action X is called
- During Action X's computation, Event A is emitted.
- An infinite loop occurs (go to Step 2)
Here's an example in code:
library <- useLibrary
useLifecycleEffect do
library.onNewInteger \newInt -> do
library.setIntValue (newInt + 1)
Consider also cases where the chain is longer and some computations run only when certain conditions are true:
- Event A is emitted
- During A's handler, Action X is called
- During Action X's computation, Event B is emitted.
- During B's handler, Action Y is called
- During Action Y's computation, Event C is emitted but only if State M is equal to 4.
- During C's handler, Action Z is called
- During Action Z's computation, Event A is emitted.
- Infinite loop may occur (depends on State M)
Re-exports from Halogen.Hooks.Extra.Hooks.UseGet
#useGet Source
useGet :: forall m a. MonadEffect m => a -> Hook m (UseGet a) (HookM m a)
Use this hook when you wish to ensure that your reference to a state
value or the component's input is not "stale" or outdated. Usually, this
happens in when you define a computation in one "Hook evaluation cycle,"
but you do not run the computation until a future "Hook evaluation cycle."
This typically occurs when running a useLifecycleEffect
/useTickEffect
's
cleanup/finalizer/unsubscribe computation.
Let's see an example of an effect's finalizer referring to a stale value
in code. If you don't use useGet
in this situation, you will refer to
what the value used to be (a stale value), not what the value is now:
myComponent :: forall q i o m. MonadAff m => H.Component HH.HTML q i o m
myComponent = Hooks.component \_ _ -> Hooks.do
thisIsFive_NotSix /\ stateId <- Hooks.useState 5
let modifyState = Hooks.modify_ stateId
Hooks.captures {} Hooks.useTickEffect do
-- The `thisIsFive_NotSix` state reference is currently `5` and
-- is up-to-date because this effect body runs immediately
-- after the Hook evaluation in which it is defined.
-- Thus, this will print "5" to the console.
logShow thisIsFive_NotSix
-- Now we change the value to 6
modifyState (_ + 1)
pure $ Just $ do
-- The effect cleanup, however, will not run after the Hook
-- evaluation in which it is defined. Thus, the `thisIsFive_NotSix`
-- state reference is still `5` even though we previously
-- updated the real value to 6.
-- Thus, this will print "5" to the console when it should print "6".
logShow thisIsFive_NotSix
To ensure we refer to the latest value and not a stale one, we use this hook to do so.
myComponent :: forall q i o m. MonadAff m => H.Component HH.HTML q i o m
myComponent = Hooks.component \_ _ -> Hooks.do
thisIsFive_NotSix /\ stateId <- Hooks.useState 5
let modifyState = Hooks.modify_ stateId
-- This returns a function to get the latest state/input value.
getState <- useGet thisIsFive_NotSix
Hooks.captures {} Hooks.useTickEffect do
logShow thisIsFive_NotSix
modifyState (_ + 1)
pure $ Just $ do
-- Now we get the latest value rather than using the stale value.
-- This correctly prints "6".
thisIsSix_NotFive <- getState
logShow thisIsSix_NotFive
Re-exports from Halogen.Hooks.Extra.Hooks.UseStateFn
#UseStateFn Source
data UseStateFn :: Type -> HookType
data UseStateFn t0
#useStateFn Source
useStateFn :: forall m a b. (StateId a -> b) -> a -> Hook m (UseStateFn a) (Tuple a b)
useStateFn
allows you to choose a MonadState
function to pair with
Hooks.useState
so you don't have to keep re-typing these functions in
your code if you only need to use one of them per piece of state.
The available functions to choose from are:
- Hooks.modify_
- Hooks.modify
- Hooks.put
- Hooks.get
For example, rather than writing:
count /\ countIdx <- Hooks.useState 42
-- ...
Hooks.modify_ countIdx (add 1)
You can write:
count /\ modifyCount <- useStateFn Hooks.modify_ 42
-- ...
modifyCount (add 1)
See these helper functions for another layer of convenience:
- useModifyState_
- useModifyState
- usePutState
#usePutState Source
usePutState :: forall m a. a -> Hook m (UseStateFn a) (Tuple a (a -> HookM m Unit))
Just like useState
, but provides a convenience function for setting
state, rather than a state index to pass to Hooks.put
.
Example:
count /\ putCount <- usePutState 42
-- ...
putCount 0
Instead of:
count /\ countIdx <- Hooks.useState 42
-- ...
Hooks.put countIdx 0
Shorthand for:
useStateFn Hooks.put
#useModifyState_ Source
useModifyState_ :: forall m a. a -> Hook m (UseStateFn a) (Tuple a ((a -> a) -> HookM m Unit))
Just like useState
, but provides a convenience function for updating
state, rather than a state index to pass to Hooks.modify_
.
Example:
count /\ modifyCount <- useModifyState_ 42
-- ...
modifyCount (add 1)
Instead of:
count /\ countIdx <- Hooks.useState 42
-- ...
Hooks.modify_ countIdx (add 1)
Shorthand for:
useStateFn Hooks.modify_
#useModifyState Source
useModifyState :: forall m a. a -> Hook m (UseStateFn a) (Tuple a ((a -> a) -> HookM m a))
Just like useState
, but provides a convenience function for updating
state, rather than a state index to pass to Hooks.modify
.
Example:
count /\ modifyCount <- useModifyState 42
-- ...
newCount <- modifyCount (add 1)
Instead of:
count /\ countIdx <- Hooks.useState 42
-- ...
newCount <- Hooks.modify countIdx (add 1)
Shorthand for:
useStateFn Hooks.modify
Re-exports from Halogen.Hooks.Extra.Hooks.UseThrottle
#UseThrottle Source
data UseThrottle :: Type -> HookType
data UseThrottle t0
#useThrottle Source
useThrottle :: forall m a. MonadAff m => Milliseconds -> (a -> HookM m Unit) -> Hook m (UseThrottle a) (a -> HookM m Unit)
Limits the amount of times an action is performed per time period. Use this hook when you need to run the same action repeatedly with a different input, but you are concerned about performance or resource usage.
Example Usage
The below example shows how to update the label with the mouse position, limiting the number of times the label is updated to once every 100ms.
myComponent = Hooks.component \_ _ -> Hooks.do
position /\ positionId <- useState { x: zero, y: zero }
throttledMouseMove <- useThrottle (Milliseconds 100.0) \e -> do
Hooks.modify_ positionId (_ { x = MouseEvent.pageX e, y = MouseEvent.pageY e}))
Hooks.pure $
HH.div
[ HE.onMouseMove $ Just <<< throttledMouseMove ]
[ HH.label_
[ HH.text $ "Mouse position: (" <>
show position.x <> ", " <> show position.y <> ")"
]
]