Deku logo

Core concepts

More hooks

Specialized hooks for faster rendering.

You're not off the hook yet! Here are some more hooks that can make your apps snappier in certain situations. Read on!


Goind on a rant

So far, we've worked with hooks like useState and useHot whose subscriptions are already memoized. That means that the same event acts as the source for all downstream consumers.

On the other hand, if you start working with events by composing them together, the subscriptions to those composed events will not be memoized. As an example, take the following snippet of code:

View on GithubVITE_START=UnMemoizedApplication pnpm example
module Examples.UnMemoizedApplication where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Array (intercalate, replicate)
import Data.Tuple (fst, snd)
import Deku.DOM.Attributes as DA
import Deku.Control (text, text_)
import Deku.DOM as D
import Deku.Do as Deku
import Deku.Hooks (useState)
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody Deku.do
  aa <- useState true
  bb <- useState false
  cc <- useState true
  dd <- useState false
  ee <- useState true
  D.div_
    [ D.div_
        ( map
            ( \i -> D.a
                [ DL.runOn DL.click $ snd i <#> not >>> fst i
                , DA.klass_ "cursor-pointer"
                ]
                [ text_ "Click me " ]
            )
            [ aa, bb, cc, dd, ee ]
        )
    , D.div_
        ( replicate 10
            ( D.div_
                [ text $
                    ( { a: _, b: _, c: _, d: _, e: _ }
                        <$> snd aa
                        <*> snd bb
                        <*> snd cc
                        <*> snd dd
                        <*> snd ee
                    )
                      <#> \{ a, b, c, d, e } ->
                        intercalate " " $ map show
                          [ a, b, c, d, e ]
                ]
            )
        )
    ]
true false true false true
true false true false true
true false true false true
true false true false true
true false true false true
true false true false true
true false true false true
true false true false true
true false true false true
true false true false true

Every line with trues and falses is responding to a different event with the following type:

{ a :: Boolean
, b :: Boolean
, c :: Boolean
, d :: Boolean
, e :: Boolean
}

While each of these events will have the same content, computing them from their constituent events wastes precious CPU cycles and can be a drain on your app. To solve this, we use a form of memoization unique to polls called ranting, and specifically the useRant hook.

The useRant hook

To make a more efficient version of the example above, we use the useRant hook. Why is it called a rant? Because it works off of a prefabbed script, aka the incoming,Poll, not unlike a rant. This is subtly different than memoization (we’ll see why later) but for now, you can think of it like memoization.

View on GithubVITE_START=MemoizedApplication pnpm example
module Examples.MemoizedApplication where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Array (intercalate, replicate)
import Data.Tuple (fst, snd)
import Deku.DOM.Attributes as DA
import Deku.Control (text, text_)
import Deku.DOM as D
import Deku.Do as Deku
import Deku.Hooks (useRant, useState)
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody Deku.do
  aa <- useState true
  bb <- useState false
  cc <- useState true
  dd <- useState false
  ee <- useState true
  composedEvent <- useRant
    ( { a: _, b: _, c: _, d: _, e: _ }
        <$> snd aa
        <*> snd bb
        <*> snd cc
        <*> snd dd
        <*> snd ee
    )
  D.div_
    [ D.div_
        ( map
            ( \i -> D.a
                [ DL.runOn DL.click $ snd i <#> not >>> fst i
                , DA.klass_ "cursor-pointer"
                ]
                [ text_ "Click me " ]
            )
            [ aa, bb, cc, dd, ee ]
        )
    , D.div_
        ( replicate 10
            ( D.div_
                [ text $
                    ( composedEvent
                        <#> \{ a, b, c, d, e } ->
                          intercalate " " $ map show
                            [ a, b, c, d, e ]
                    )
                ]
            )
        )
    ]

Now, each subscription to composedEvent will draw from the same event. This can help avoid lag, especially when belaboring under the constraint of a rendering loop that needs to execute at 60fps.

Ranting without an initial event

Sometimes, rants just come outta nowhere. For example, if you’re building a question-answer app that needs to perform a computation and compute the response. In these cases, you can use useRant'. This hook provides the opportunity to map over the internal event, and it is the result of the mapped computation that is memoized.

In the example below, this method is used to memoize a squaring operation so that the result is computed only once.

View on GithubVITE_START=MemoizedNoEvent pnpm example
module Examples.MemoizedNoEvent where

import Prelude
import Deku.Toplevel (runInBody)

import Control.Alt ((<|>))
import Data.Array (replicate)
import Data.Int (floor, pow)
import Data.Tuple.Nested ((/\))
import Deku.Control (text)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Do as Deku
import Deku.Hooks (useRant')
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

inputKls :: String
inputKls =
  """rounded-md
border-gray-300 shadow-sm
border-2 mr-2
border-solid
focus:border-indigo-500 focus:ring-indigo-500
sm:text-sm"""

main :: Effect Unit
main = void $ runInBody Deku.do
  setNumber /\ number <- useRant' (map (_ `pow` 2))
  D.div_
    [ D.div_
        [ D.input
            [ DA.klass_ inputKls
            , DA.xtypeNumber
            , DA.min_ "0"
            , DA.max_ "100"
            , DA.value_ "0"
            , DL.numberOn_ DL.change (floor >>> setNumber)
            ]
            []
        ]
    , D.div_
        ( replicate 200 $ D.span_
            [ text (show >>> (_ <> " ") <$> (pure 0 <|> number)) ]
        )
    ]
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

The useHotRant hook

useRant has another superpower that will become evident when we learn about fix and fold in later sections. folds always start anew at the point of usage, which means that if a fold is an accumulator, it will start accumulating from scratch everywhere it is used. But sometimes, you want a fold to contain its most-recent value.useRant is handy in these situations.

View on GithubVITE_START=FoldingRants pnpm example
module Examples.FoldingRants where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Tuple.Nested ((/\))
import Deku.Control (text, text_)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Do as Deku
import Deku.Hooks (guard, useHot, useRant, useState', (<#~>))
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Event (fold)
import Deku.Toplevel (runInBody)

buttonClass =
  """inline-flex items-center rounded-md
border border-transparent bg-indigo-600 px-3 py-2
text-sm font-medium leading-4 text-white shadow-sm
hover:bg-indigo-700 focus:outline-none focus:ring-2
focus:ring-indigo-500 focus:ring-offset-2 mr-6""" :: String

main :: Effect Unit
main = do
  void $ runInBody Deku.do
    thunkMe /\ thunked <- useState'
    setPresence /\ presence <- useHot false
    let counter = fold (\a _ -> a + 1) 0 thunked
    ranted <- useRant counter
    let
      framed = D.div [ DA.klass_ "border-4" ]
        [ D.p_
            [ text_ "Ranted: "
            , text $ show <$> ranted
            ]
        , D.p_
            [ text_ "Normal: "
            , text $ show <$> counter
            ]
        ]
    D.div_
      [ D.button
          [ DA.klass_ buttonClass
          , DL.click_ \_ -> thunkMe unit
          ]
          [ text_ "Update number" ]
      , ranted <#~> \_ -> D.button
          [ DA.klass_ buttonClass
          , DL.runOn DL.click $ presence <#> not >>> setPresence
          ]
          [ text $ presence <#> if _ then "Hide" else "Show" ]
      , framed
      , guard presence framed
      ]

Ranted:

Normal:

There is even a useHotRant hook that, like useHot will play back the most recent value as well. Because the only thing that's better than a rant is a hot rant.

View on GithubVITE_START=FoldingHotRants pnpm example
module Examples.FoldingHotRants where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Tuple.Nested ((/\))
import Deku.Control (text, text_)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Do as Deku
import Deku.Hooks (guard, useHot, useHotRant, useState', (<#~>))
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Event (fold)
import Deku.Toplevel (runInBody)

buttonClass =
  """inline-flex items-center rounded-md
border border-transparent bg-indigo-600 px-3 py-2
text-sm font-medium leading-4 text-white shadow-sm
hover:bg-indigo-700 focus:outline-none focus:ring-2
focus:ring-indigo-500 focus:ring-offset-2 mr-6""" :: String

main :: Effect Unit
main = do
  void $ runInBody Deku.do
    thunkMe /\ thunked <- useState'
    setPresence /\ presence <- useHot false
    let counter = fold (\a _ -> a + 1) 0 thunked
    ranted <- useHotRant counter
    let
      framed = D.div [ DA.klass_ "border-4" ]
        [ D.p_
            [ text_ "Ranted: "
            , text $ show <$> ranted
            ]
        , D.p_
            [ text_ "Normal: "
            , text $ show <$> counter
            ]
        ]
    D.div_
      [ D.button
          [ DA.klass_ buttonClass
          , DL.click_ \_ -> thunkMe unit
          ]
          [ text_ "Update number" ]
      , ranted <#~> \_ -> D.button
          [ DA.klass_ buttonClass
          , DL.runOn DL.click $ presence <#> not >>> setPresence
          ]
          [ text $ presence <#> if _ then "Hide" else "Show" ]
      , framed
      , guard presence framed
      ]

Ranted:

Normal:

The useDeflect hook

In the previous section, we saw that useRant is almost memoization, but not quite. Full-blown memoization doesn't really exist or make sense for Polls because of their Applicative instance: what does it mean to memoize pure 1? Does it mean that, every time we consult the Poll, it should respond with 1 right off the bat? Or does it mean that it should respond with 1at a given point in time and then no longer, because that time has passed? If so, what point in time are we talking about? So many questions!

The useRant hook sidesteps these issues by ignoring a Poll's purity. For example useRant (pure 1 <|> pure 2) will emit nothing.OTOH, useRant (pure 1 <|> pure 2 <|> e) will emit everyhting from e that's not pure and nothing else.

Usually, this feature of useRant is not an issue because we’re dealing with some sort of stream like clicks and the initial value is context specific, so purity doesn't enter the equation. However, sometimes we want to find only the pure values of a Poll. For this, we have the useDeflect hook.

View on GithubVITE_START=TheUseDeflectHook pnpm example
module Examples.TheUseDeflectHook where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Tuple.Nested ((/\))
import Deku.Control (text, text_)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Do as Deku
import Deku.Hooks (guard, useDeflect, useHot, useRant, useState, (<#~>))
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Event (fold)
import Deku.Toplevel (runInBody)

buttonClass =
  """inline-flex items-center rounded-md
border border-transparent bg-indigo-600 px-3 py-2
text-sm font-medium leading-4 text-white shadow-sm
hover:bg-indigo-700 focus:outline-none focus:ring-2
focus:ring-indigo-500 focus:ring-offset-2 mr-6""" :: String

main :: Effect Unit
main = do
  void $ runInBody Deku.do
    thunkMe /\ thunked <- useState unit
    setPresence /\ presence <- useHot false
    let counter = fold (\a _ -> a + 1) 0 thunked
    deflected <- useDeflect counter
    ranted <- useRant counter
    let
      framed = D.div [ DA.klass_ "border-4" ]
        [ D.p_
            [ text $ deflected <#>
                show >>> ("Deflected: " <> _)
            ]
        , D.p_
            [ text $ ranted <#>
                show >>> ("Ranted: " <> _)
            ]
        , D.p_
            [ text $ counter <#>
                show >>> ("Normal: " <> _)
            ]
        ]
    D.div_
      [ D.button
          [ DA.klass_ buttonClass
          , DL.click_ \_ -> thunkMe unit
          ]
          [ text_ "Update number" ]
      , ranted <#~> \_ -> D.button
          [ DA.klass_ buttonClass
          , DL.runOn DL.click $ presence <#> not >>> setPresence
          ]
          [ text $ presence <#> if _ then "Hide" else "Show" ]
      , framed
      , guard presence framed
      ]

Deflected: 1

Normal: 1

The useSkimmed hook

Lastly, for you performance nerds, useSkimmed is like useRant except that it emits the final initial event if one is present. This may seem a bit counterintuitive, as the point of useRant is to memoize the dynamic bits and lob off initial pure values so that there isn't a burst of activity on subscription. However, there are times when you want to avoid the flurry of initial activity except the last initial event, which one can then choose to use or discard. The useSkimmed hook skims off all but the last initial event.


Use mailboxed

In web apps, there are many cases where we receive information from a REST or GraphQL API that applies to a specific component in a large collection of Deku components, for example a game lobby picker or a friends list. In these cases, it is inefficient for every component in the collection to listen to the update. Instead, we want only the relevant components to listen. For these cases, there's useMailboxed.

Hello mailbox

A mailbox hook is similar to the useState hook.

pusher /\ eventCreator <- useMailboxed

However, the left and right side of the returned value are different.

  1. On the left, we have a pusher that takes a record of type { address :: a, payload :: b }.
  2. On the right, we have an event creator that takes a value of type a and produces an event of type Event b.

When the pusher is pushed to, the mailbox delivers payloads of type b only to events that have been created with by invoking the event creator with the same term of type a that was received by the pusher as an address. You can see an example below.

View on GithubVITE_START=UseMailboxed pnpm example
module Examples.UseMailboxed where

import Prelude
import Deku.Toplevel (runInBody)

import Control.Alt ((<|>))
import Data.Array ((..))
import Data.Tuple.Nested ((/\))
import Deku.DOM.Attributes as DA
import Deku.Control (text_)
import Deku.DOM as D
import Deku.Do as Deku
import Deku.Hooks (useMailboxed', useState)
import Deku.DOM.Listeners as DL
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

buttonClass =
  """inline-flex items-center rounded-md
border border-transparent bg-indigo-600 px-3 py-2
text-sm font-medium leading-4 text-white shadow-sm
hover:bg-indigo-700 focus:outline-none focus:ring-2
focus:ring-indigo-500 focus:ring-offset-2 mr-6""" :: String

main :: Effect Unit
main = void $ runInBody Deku.do
  setInt /\ int <- useState 0
  setMailbox /\ mailbox <- useMailboxed'
  D.div_
    [ D.button
        [ DA.klass_ buttonClass
        , DL.runOn DL.click $ int <#> \i -> do
            setMailbox { address: i, payload: unit }
            setInt ((i + 1) `mod` 100)
        ]
        [ text_ "Bang!" ]
    , D.div_
        ( (0 .. 99) <#> \n -> D.span
            [ DA.klass $ (pure false <|> (mailbox n $> true)) <#>
                if _ then "" else "hidden"
            ]
            [ text_
                ( ( if n == 99 then "We're done here"
                    else show n
                  ) <> " "
                )
            ]
        )
    ]

Mailboxes are filters with superpowers

While it's possible to achieve a similar outcome with Filtering, it's much slower. Individual filters for a collection of n items execute in O(n) time because each entry has to check if it has a valid input.

With mailbox, the postwoman is pre-sorting the mail and only delivering it to valid destinations. The sorting function executes in O(log n) time thanks to the Ord constraint on the input. So when working with collections where you’ll have to update a single addressable element in response to an event, use mailboxes!


Use ref

The useMailboxed hook has a spiritual cousin of sorts called useRef. They both exist as performance optimizations. As you learned in the last section, useMailboxed is used to reduce the number of subscriptions to an event. useRef takes this one step further, reducing the number of subscriptions to one. If you can get the number lower, I’ll eat my hat!

What's in a ref?

The useRef hook takes an initial value of typea and an event of type Event a and returns a value of type Effect a that is a reference to the most recent value of the event or the initial value if the event has never fired.

In the example below, slide the slider from left to right and click on buttons periodically. Each button will update with the most recent value of the slider when it is clicked.

View on GithubVITE_START=UseRef pnpm example
module Examples.UseRef where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Array (replicate)
import Data.Tuple.Nested ((/\))
import Deku.Control (text)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Do as Deku
import Deku.Hooks (useRef, useState)
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

buttonClass =
  """inline-flex items-center rounded-md
border border-transparent bg-pink-600 px-3 py-2
text-sm font-medium leading-4 text-white shadow-sm
hover:bg-pink-700 focus:outline-none focus:ring-2
focus:ring-pink-500 focus:ring-offset-2 m-2""" :: String

main :: Effect Unit
main = void $ runInBody Deku.do
  let initial = 50.0
  setNum /\ num <- useState initial
  intRef <- useRef initial num
  D.div_
    [ D.input [ DA.xtypeRange, DL.numberOn_ DL.input setNum ]
        []
    , D.div [ DA.klass_ "grid grid-flow-row grid-cols-3" ]
        ( replicate 24 Deku.do
            setButtonText /\ buttonText <- useState "Waiting..."
            D.button
              [ DA.klass_ buttonClass
              , DL.click_ \_ -> intRef >>= show >>> setButtonText
              ]
              [ text buttonText ]
        )
    ]

Performance considerations

The alternative to useRef is pandemonium and chaos. No, just kidding, it's using an event to replace a listener.

In the example above, instead of DL.runOn DL.click $ intRef >>= show >>> setButtonText, we could have written DL.runOn DL.click $ num <#> show >>> setButton. While the functionality would be the same, the performance would be much worse, as every click listener would be updated every time the slider moved. Who wants that? So use useRef whenever you have a lot of listeners that depend on the content of a single value emitted by an event.


Custom hooks

Sometimes, Deku's amazingly versatile, hand crafted, meticulously curated library of hooks is not good enough for our most discerning, demanding, and sophisticated users. If you are in that category, read on to learn how to make custom hooks!

Hooked on hooks

The first step on your journey to a custom hook is saying to yourself “I need a custom hook.” Now that you've gotten that out of the way, Deku provides a type Hook a that allows you to construct a hook of type a. In its simplest form, it looks like this:

View on GithubVITE_START=CustomHook1 pnpm example
module Examples.CustomHook1 where

import Prelude
import Deku.Toplevel (runInBody)

import Deku.Control (text_)
import Deku.Core (Hook)
import Deku.DOM as D
import Deku.Do as Deku
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody Deku.do
  let
    myFortyTwoHook :: Hook String
    myFortyTwoHook makeHook = makeHook "forty-two"
  fortyTwo <- myFortyTwoHook
  D.div_
    [ text_ fortyTwo ]
forty-two

Note that the Hook type constructor is actually a function that takes the hook creator as an argument and returns whatever is in your hook passed through that creator. The flow may seem a bit odd at first, but you’ll get the hang of it! Just remember: to make a hook, make sure to accept a function as the first parameter and, as the last step, apply that function to the content of your hook. In the example above, we call the function makeHook, but it could just as well be called cruise or nicholson.

View on GithubVITE_START=CustomHook2 pnpm example
module Examples.CustomHook2 where

import Prelude
import Deku.Toplevel (runInBody)

import Assets (cruiseURL, nicholsonURL)
import Deku.Control (text_)
import Deku.Core (Hook)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.Do as Deku
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

main :: Effect Unit
main = void $ runInBody Deku.do
  let
    hook1 :: Hook Boolean
    hook1 cruise = cruise true

    hook2 :: Hook Boolean
    hook2 nicholson = nicholson true
  r1 <- hook1
  r2 <- hook2
  D.div_
    [ D.p_ [ text_ "I want the ", D.code__ $ show r1, text_ "th!" ]
    , D.img
        [ DA.alt_ "A picture of Tom Cruise in A Few Good Men"
        , DA.src_ cruiseURL
        ]
        []
    , D.p_
        [ text_ "You can't handle the "
        , D.code__ $ show r2
        , text_ "th!"
        ]
    , D.img
        [ DA.alt_ "A picture of Jack Nicholson in A Few Good Men"
        , DA.src_ nicholsonURL
        ]
        []
    ]

I want the trueth!

A picture of Tom Cruise in A Few Good Men

You can't handle the trueth!

A picture of Jack Nicholson in A Few Good Men

Deku dos and nested hooks

What fun would custom hooks be if they couldn't nest other hooks? When nesting hooks, Deku.do is your friend.

View on GithubVITE_START=NestedCustomHooks pnpm example
module Examples.NestedCustomHooks where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Tuple.Nested (type (/\), (/\))
import Deku.Control (text, text_)
import Deku.Core (Hook)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Do as Deku
import Deku.Hooks (useHotRant, useState)
import Deku.Toplevel (runInBody)
import Effect (Effect)
import ExampleAssitant (ExampleSignature)
import FRP.Poll (Poll)
import Deku.Toplevel (runInBody)

buttonClass =
  """inline-flex items-center rounded-md
border border-transparent bg-pink-600 px-3 py-2
text-sm font-medium leading-4 text-white shadow-sm
hover:bg-pink-700 focus:outline-none focus:ring-2
focus:ring-pink-500 focus:ring-offset-2 m-2""" :: String

main :: Effect Unit
main = void $ runInBody Deku.do
  let
    hookusMinimus :: Int -> Hook ((Int -> Effect Unit) /\ Poll Int)
    hookusMinimus i makeHook = Deku.do
      setMinimus /\ minimus <- useState i
      makeHook (setMinimus /\ minimus)

    hookusMaximus
      :: Int
      -> Hook ((Int -> Effect Unit) /\ Poll Int /\ Poll Int)
    hookusMaximus i makeHook = Deku.do
      setMinimus /\ minimus <- hookusMinimus i
      let added = add 1000 <$> minimus
      maximus <- useHotRant added
      makeHook (setMinimus /\ minimus /\ maximus)
  setMinimus /\ minimus /\ maximus <- hookusMaximus 0
  D.div_
    [ D.button
        [ DA.klass_ buttonClass
        , DL.runOn DL.click $ minimus <#> (add 1 >>> setMinimus)
        ]
        [ text_ "Increment" ]
    , D.div_
        [ text_ "Hookus minimus: "
        , text (show <$> minimus)
        ]
    , D.div_
        [ text_ "Hookus maximus: "
        , text (show <$> maximus)
        ]
    ]
Hookus minimus: 0
Hookus maximus: 1000

Faux hooks

They look like hooks, they feel like hooks, but they’re not hooks! Deku has a Deku.Effect module that, in general, has API parity withDeku.Hooks. The difference is, instead of using them in a Deku.do block, you use them in a plain olddo block for Effect or ST Global. The reason for this is that sometimes we need to use hook-like things at the top level of an application. This allows us to have code that exudes Deku-ness throught a full application.

View on GithubVITE_START=FauxHooks pnpm example
module Examples.FauxHooks where

import Prelude
import Deku.Toplevel (runInBody)

import Data.Tuple.Nested ((/\))
import Deku.Control (text, text_)
import Deku.DOM as D
import Deku.DOM.Attributes as DA
import Deku.DOM.Listeners as DL
import Deku.Do as Deku
import Deku.Effect as DE
import Deku.Hooks as DH
import Deku.Toplevel (runInBody)
import Effect (Effect)
import Effect.Random (random)
import ExampleAssitant (ExampleSignature)
import Deku.Toplevel (runInBody)

buttonClass =
  """inline-flex items-center rounded-md
border border-transparent bg-indigo-600 px-3 py-2
text-sm font-medium leading-4 text-white shadow-sm
hover:bg-indigo-700 focus:outline-none focus:ring-2
focus:ring-indigo-500 focus:ring-offset-2 mr-6""" :: String

main :: Effect Unit
main = do
  nD <- random
  setNumberE /\ numberE <- random >>= DE.useState
  void $ runInBody Deku.do
    setNumberD /\ numberD <- DH.useState nD
    D.div_
      [ D.button
          [ DA.klass_ buttonClass
          , DL.click_ \_ ->
              let go x = random >>= x in go setNumberE *> go setNumberD
          ]
          [ text_ "Update number" ]
      , D.div_
          [ text $ numberD <#>
              show >>> ("Here's a random number: " <> _)
          ]
      , D.div_
          [ text $ numberE <#>
              show >>> ("Here's a random number: " <> _)
          ]
      ]
Here's a random number: 0.21168591147384186
Here's a random number: 0.5643144220684508
Previous
Providers