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:
VITE_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 truetrue false true false truetrue false true false truetrue false true false truetrue false true false truetrue false true false truetrue false true false truetrue false true false truetrue false true false truetrue false true false true
Every line with true
s and false
s 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.
VITE_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.
VITE_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.
VITE_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.
VITE_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 Poll
s 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 1
at 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.
VITE_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.
- On the left, we have a pusher that takes a record of type
{ address :: a, payload :: b }
. - On the right, we have an event creator that takes a value of type
a
and produces an event of typeEvent 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.
VITE_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
) <> " "
)
]
)
]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 We're done here
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.
VITE_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:
VITE_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
.
VITE_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
true
th!You can't handle the
true
th!
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.
VITE_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: 0Hookus 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.
VITE_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.21168591147384186Here's a random number: 0.5643144220684508