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
useDebouncer :: forall a m. 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 /\ tLabel <- useState ""
makeNewSearchFor <- useDebouncer (Milliseconds 500.0) \finalValue -> do
Hooks.put tLabel 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 hooks a m. MonadEffect m => Hooked m hooks (UseEvent m a hooks) (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 /\ tState <- 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 a m. 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 /\ modifyState <- Hooks.useState 5
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 /\ modifyState <- Hooks.useState 5
-- 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
useStateFn :: forall b a m. (StateId a -> b) -> a -> Hook m (UseStateFn a) (Tuple a b)
Rather than writing this...
state /\ stateId <- useState 0
let modifyState = Hooks.modify_ stateId
pure (state /\ modifyState)
... one can write this code:
state /\ modifyState <- useStateFn Hooks.modify_ 0
See also useModifyState
, which makes this even less boilerplate-y.
The function argument should be one of these four functions:
- Hooks.modify_
- Hooks.modify
- Hooks.put
- Hooks.get
#useModifyState Source
useModifyState :: forall a m. a -> Hook m (UseStateFn a) (Tuple a ((a -> a) -> HookM m Unit))
Re-exports from Halogen.Hooks.Extra.Hooks.UseThrottle
#useThrottle Source
useThrottle :: forall a m. 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 /\ modifyPosition <- useState { x: zero, y: zero }
throttledMouseMove <- useThrottle (Milliseconds 100.0) (\e -> modifyPosition (_ { 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 <> ")" ]
]