Module

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 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

#UseEventApi Source

type UseEventApi :: (Type -> Type) -> Type -> Typetype UseEventApi m a = { push :: a -> HookM m Unit, setCallback :: Maybe (((HookM m (HookM m Unit)) -> HookM m Unit) -> a -> HookM m Unit) -> HookM m (HookM m Unit) }

For proper usage, see the docs for useEvent.

#UseEvent Source

data UseEvent :: (Type -> Type) -> Type -> HookTypedata UseEvent t0 t1

#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:

  1. Event A is emitted
  2. During A's handler, Action X is called
  3. During Action X's computation, Event A is emitted.
  4. 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:

  1. Event A is emitted
  2. During A's handler, Action X is called
  3. During Action X's computation, Event B is emitted.
  4. During B's handler, Action Y is called
  5. During Action Y's computation, Event C is emitted but only if State M is equal to 4.
  6. During C's handler, Action Z is called
  7. During Action Z's computation, Event A is emitted.
  8. Infinite loop may occur (depends on State M)

Re-exports from Halogen.Hooks.Extra.Hooks.UseGet

#UseGet Source

data UseGet :: Type -> HookTypedata UseGet t0

#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

#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

#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 <> ")"
        ]
      ]