Skip to content

Commit

Permalink
Adds useEffect hook (#88)
Browse files Browse the repository at this point in the history
* Adds useEffect

* Better fiber management

* Uses st ref

* Adds docstrings
  • Loading branch information
Mike Solomon authored Mar 9, 2023
1 parent ef818f5 commit 23240ea
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 5 deletions.
13 changes: 13 additions & 0 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -233,5 +233,18 @@ describe('deku', () => {
expect($('#b3').text()).toBe('2');
expect($('#b4').text()).toBe('');
}));

doTest('useEffect has correct behavior', (f) => f(tests.useEffectWorks, () => {
const $ = require('jquery');
expect($('#mydiv').text()).toBe('0');
$('#counter').trigger('click');
expect($('#mydiv').text()).toBe('2');
$('#counter').trigger('click');
expect($('#mydiv').text()).toBe('3');
$('#counter').trigger('click');
expect($('#mydiv').text()).toBe('4');
$('#counter').trigger('click');
expect($('#mydiv').text()).toBe('6');
}));
});

2 changes: 1 addition & 1 deletion src/Deku/Do.purs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ bind :: forall a r q. ((a -> r) -> q) -> (a -> r) -> q
bind f a = f a

-- we do not use any warnings for discard
discard :: forall a r q. ((a -> r) -> q) -> (a -> r) -> q
discard :: forall r q. ((Unit -> r) -> q) -> (Unit -> r) -> q
discard = bind
48 changes: 45 additions & 3 deletions src/Deku/Hooks.purs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ module Deku.Hooks
, useMemoized'
, useMemoized
, useMailboxed
, useEffect
, useAff
, useAffWithCancellation
, useHot
, useHot'
, useDyn
Expand All @@ -37,7 +40,9 @@ import Data.Tuple.Nested (type (/\), (/\))
import Deku.Core (Domable(..), Node, bus, bussedUncurried, insert, remove, sendToPos)
import Deku.Do as Deku
import Effect (Effect)
import FRP.Event (Event, Subscriber(..), createPure, keepLatest, mailboxed, makeLemmingEvent, makeLemmingEventO, memoize)
import Effect.Aff (Aff, error, killFiber, launchAff, launchAff_)
import Effect.Uncurried (mkEffectFn1, runEffectFn1, runEffectFn2)
import FRP.Event (Event, Subscriber(..), createPure, keepLatest, mailboxed, makeEventO, makeLemmingEvent, makeLemmingEventO, memoize, subscribeO)
import Safe.Coerce (coerce)

-- | A state hook for states without initial values. See [`useState'`](https://purescript-deku.netlify.app/core-concepts/state#state-without-initial-values) in the Deku guide for example usage.
Expand Down Expand Up @@ -89,7 +94,7 @@ useRef
:: forall lock payload a
. a
-> Event a
-> ( Effect a
-> ( Effect a
-> Domable lock payload
)
-> Domable lock payload
Expand Down Expand Up @@ -196,4 +201,41 @@ useHot a f = Domable $ envy $ makeLemmingEventO
)
runSTFn1 k ((\(Domable x) -> x) (f (push'' /\ event')))
runSTFn2 s event (mkSTFn1 \v -> void $ writeVal v)
)
)

-- | A hook that runs an arbitrary effect when an event's value changes.
useEffect
:: forall lock payload a
. Event a
-> (a -> Effect Unit)
-> (Unit -> Domable lock payload)
-> Domable lock payload
useEffect e f1 f2 = Domable $ envy $ coerce $ makeEventO $ mkEffectFn1 \k -> do
runEffectFn1 k (f2 unit)
runEffectFn2 subscribeO e $ mkEffectFn1 f1

-- | A hook that runs an arbitrary aff when an event's value changes.
useAff
:: forall lock payload a
. Event a
-> (a -> Aff Unit)
-> (Unit -> Domable lock payload)
-> Domable lock payload
useAff e = useEffect e <<< map launchAff_

-- | A hook that runs an arbitrary aff when an event's value changes, cancelling the previous aff.
useAffWithCancellation
:: forall lock payload a
. Event a
-> (a -> Aff Unit)
-> (Unit -> Domable lock payload)
-> Domable lock payload
useAffWithCancellation e f1 f2 = Domable $ envy $ coerce $ makeEventO $ mkEffectFn1 \k -> do
r <- liftST $ STRef.new (pure unit)
runEffectFn1 k (f2 unit)
runEffectFn2 subscribeO e $ mkEffectFn1 \a -> do
r' <- liftST $ STRef.read r
r'' <- launchAff do
killFiber (error "useAffWithCancellation") r'
f1 a
liftST $ void $ STRef.write r'' r
19 changes: 18 additions & 1 deletion test/Test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Deku.Control (blank, globalPortal1, switcher, text, text_)
import Deku.Core (Domable, Nut, dyn, fixed, insert, insert_, sendToPos)
import Deku.DOM as D
import Deku.Do as Deku
import Deku.Hooks (useMemoized, useRef, useState, useState')
import Deku.Hooks (useEffect, useMemoized, useRef, useState, useState')
import Deku.Interpret (FFIDOMSnapshot, Instruction)
import Deku.Lifecycle (onDidMount, onDismount, onWillMount)
import Deku.Listeners (click, click_)
Expand Down Expand Up @@ -322,4 +322,21 @@ useRefWorks = Deku.do
)
[ text (show <$> buttonTxt) ]
)
]

useEffectWorks :: Nut
useEffectWorks = Deku.do
let startsAt = 0
setCounter /\ counter <- useState startsAt
useEffect counter \i -> when (i `mod` 4 == 1) do
setCounter (i + 1)
D.div_
[ D.button
( merge
[ click $ counter <#> add 1 >>> setCounter
, id_ "counter"
]
)
[ text_ "Increment" ]
, D.div (id_ "mydiv") [ text (show <$> counter) ]
]

0 comments on commit 23240ea

Please sign in to comment.