thomashoneyman/purescript-halogen-hooks

Multiple uses of `useMemo` reevaluates values with unrelated dependencies

JordanMartinez opened this issue · 0 comments

Using this code:

module Test.UseMemo where

import Prelude

import Data.Maybe (Maybe(..))
import Data.Symbol (SProxy(..))
import Data.Tuple.Nested ((/\))
import Effect.Console as Console
import Effect.Unsafe (unsafePerformEffect)
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 "useMemo"
sproxy = SProxy

component :: forall q o m. H.Component HH.HTML q Unit o m
component = Hooks.component \_ -> hook

hook :: forall slots output m hooks
      . Hooked slots output m hooks (Hooks.UseMemo Int (Hooks.UseMemo Int ((Hooks.UseMemo Int (Hooks.UseState Int (Hooks.UseState Int hooks)))))) _
hook = Hooks.do
  s1 /\ ts1 <- Hooks.useState 0
  s2 /\ ts2 <- Hooks.useState 0

  expensiveValue1 <- memoize1 { s1 }

  expensiveValue2 <- memoize2 { s2 }

  expensiveValue3 <- memoize3 { s1, s2 }

  Hooks.pure $
    HH.div_
      [ HH.ol_
        [ HH.li_ [ HH.text $ "Expensive value 1 is: " <> show expensiveValue1 ]
        , HH.li_ [ HH.text $ "Expensive value 2 is: " <> show expensiveValue2 ]
        , HH.li_ [ HH.text $ "Expensive value 3 is: " <> show expensiveValue3 ]
        ]
      , HH.button
        [ HE.onClick \_ -> Just $ Hooks.modify_ ts1 (_ + 1)
        ]
        [ HH.text $
            "Increase `s1` by 1, which will recompute 1 and 3."
        ]
      , HH.br_
      , HH.button
        [ HE.onClick \_ -> Just $ Hooks.modify_ ts2 (_ + 1)
        ]
        [ HH.text $
            "Increase `s2` by 1, which will recompute 2 and 3."
        ]
      ]
  where
    memoize1 deps@{s1} = Hooks.captures deps $ flip Hooks.useMemo \_ ->
      let _ = unsafePerformEffect (Console.log "useMemo: recalculating expensive value1")
      in s1 + 5

    memoize2 deps@{s2} = Hooks.captures deps $ flip Hooks.useMemo \_ ->
      let _ = unsafePerformEffect (Console.log "useMemo: recalculating expensive value2")
      in s2 + 5

    memoize3 deps@{s1, s2} = Hooks.captures deps $ flip Hooks.useMemo \_ ->
      let _ = unsafePerformEffect (Console.log "useMemo: recalculating expensive value3")
      in s1 + s2 + 5

There's two ways to produce a bug here:
First, I get these results:

-- on initial render, I get what I expect
useMemo: recalculating expensive value1
useMemo: recalculating expensive value2
useMemo: recalculating expensive value3
-- I click button 1, which gets what I expect
useMemo: recalculating expensive value1
useMemo: recalculating expensive value3
-- I click button 1, which unexpectedly evaluates the second memo
useMemo: recalculating expensive value1
useMemo: recalculating expensive value2 -- <-
useMemo: recalculating expensive value3
-- I continuously click button 1, and this cycle repeats:
--   1 and 3 are reevaluated
--   1, 2, and 3 are reevaluated

Second, I get these results:

-- on initial render, I get what I expect
useMemo: recalculating expensive value1
useMemo: recalculating expensive value2
useMemo: recalculating expensive value3
-- I click button 1, which gets what I expect
useMemo: recalculating expensive value1
useMemo: recalculating expensive value3
-- I click button 2, which unexpectedly evaluates the first memo
useMemo: recalculating expensive value1 -- <-
useMemo: recalculating expensive value2
useMemo: recalculating expensive value3
-- I continuously click button 2 and only value2 and value 3 get reevaluated

The third approach does not produce a bug:

-- on initial render, I get what I expect
useMemo: recalculating expensive value1
useMemo: recalculating expensive value2
useMemo: recalculating expensive value3
-- I click button 2, which gets what I expect
useMemo: recalculating expensive value2
useMemo: recalculating expensive value3
-- I continuously click button 2 and only value2 and value 3 get reevaluated

I suspect that it has something to do with the index not being incremented when the memos did not change (i.e. the else branch) when they are updated in the then branch...?