useTickEffect finalizers don't get run when dependencies change
JordanMartinez opened this issue · 1 comments
Given this code:
module Test.UseTickEffect where
import Prelude
import Data.Maybe (Maybe(..))
import Data.Symbol (SProxy(..))
import Data.Tuple.Nested ((/\))
import Effect.Class (class MonadEffect)
import Effect.Class.Console (log)
import Halogen (liftEffect)
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.Hooks (Hooked)
import Halogen.Hooks as Hooks
sproxy :: SProxy "useTickEffect"
sproxy = SProxy
component :: forall q o m. MonadEffect m => H.Component HH.HTML q Unit o m
component = Hooks.component \_ -> hook
hook :: forall slots output m hooks. MonadEffect m
=> Hooked slots output m hooks (Hooks.UseEffect (Hooks.UseState Int hooks)) _
hook = Hooks.do
state /\ tState <- Hooks.useState 0
Hooks.captures { state } Hooks.useTickEffect do
liftEffect $ log $
"useTickEffect: This message appears in two situations. First, when the \
\component is initialized. Second, every time the state value changes, \
\but it appears only AFTER the cleanup message appears."
pure $ Just do
liftEffect $ log $
"useTickEffect: [Cleanup Message]. This message appears in two \
\situations. First, every time the state value changes. Second, \
\when component is removed."
Hooks.pure $
HH.div_
[ HH.p_
[ HH.text $ "Click the button to change the value of the dependency, \
\which will trigger the useTickEffect code."
]
, HH.button
[ HE.onClick \_ -> Just $ Hooks.modify_ tState (_ + 1) ]
[ HH.text $ "Trigger the `useTickEffect` code by \
\increasing the state value by 1"
]
]
The finalizer doesn't get run when the state changes.
The issue is that the tick effect's finalizers are stored in the same array as a lifecycle effects' finalizers. Thus, when the dependency changes, none of the tick finalizers can be run without also running the lifecycle finalizers.
My initial thought for solving this problem was to change the type signature for the effectCells
row to store both the MemoValues
and the Finalizer
. This was inspired by how memoCells
stores both the MemoValues
and MemoValue
:
type InternalHookState q i ps o m =
{ -- ...
-- inspiration from memoCells
, memoCells :: QueueState (MemoValues /\ MemoValue) -- the memo values via useMemo
-- before: stores only memo values
, effectCells :: QueueState MemoValues
-- after: stores memo values and the finalizer to run when memos changes
, effectCells :: QueueState (MemoValues /\ HookM ps o m Unit)
}
When I tried to do this, I ran into a problem. At some point, this code is called:
newQueue = unsafeSetCell index (memos /\ finalizer) queue
This is problematic because I don't have a reference to the new finalizer at this point. For context, a finalizer
gets created when we initially run the effect. In some situations, we may wish to change what that finalizer is, so we cannot reuse the finalizer we created when we originally initialized it:
mbFinalizer <- evalHookM (interpretUseHookFn Queued hookFn) act
During the Step
case, we enqueue the above effect and run it later. It is during this evaluation (I believe) where the initial effect gets rerun, which produces the new finalizer:
let eval = void $ evalHookM (runUseHookFn Queued hookFn) act
modifyState_ \st -> st { evalQueue = Array.snoc st.evalQueue eval }
I'm not sure what would be the best way to get this new finalizer.