React.Basic.Hooks: state value does not change in eventHandler

The state variable does not seem to change in setInterval handler:

    thisDay /\ setThisDay <- useState' (Nothing :: Maybe Date)
    -- ....
    let checkDateChange = do
            today <- Just <$> nowDate
            Console.log $ i "thisDay=" (show thisDay) ", nowDate=" (show today)
            if today /= thisDay -- <<<< thisDay always seems Nothing here
                then setThisDay today
                else pure unit
    -- ....
    useEffectOnce do
        checkDateChange
        timerId <- setInterval 1000 $ checkDateChange
        pure $ clearInterval timerId
    -- ...
    let format2 = _.after <<< (\s ->  splitAt (length s - 2) s) <<< ("00" <> _) <<< show
        todayStr = fromMaybe "2021-12-31"
            $ (\d -> i (show $ fromEnum $ year d)
                "-" (format2 $ fromEnum $ month d)
                "-" (format2 $ fromEnum $ day d)
                )
            <$> thisDay
    pure $ R.div
        { --....
        , children:
            -- ....
                    , R.input
                        { type: "date"
                        , id: "valueDate"
                        , value: trace todayStr \_ -> todayStr
        -- ...

I don’t want input to rerender every time. So compare thisDay to new date in order not to change thisDay every second. But every time checkDateChange fires, thisDay seems Nothing in the function. I feel this is something to do with the closure of the checkDateChange but I can’t figure out how to circumvent this situation.

Thanks in advance.

Sorry, I forgot to mention: The value actually changes and trace logs date
every second on R.input value. But in handler it does not change.

You have this code:

    useEffectOnce do
        checkDateChange
        timerId <- setInterval 1000 $ checkDateChange
        pure $ clearInterval timerId

So once when the component first mounts, you register checkDateChange as to run every second, and checkDateChange captures the current thisDay state variable. When the state changes, it executes the entire React.do block again, this time with the new state value, but it doesn’t rerun that Effect, since that only happens once when the component first mounts, and the Effect is still holding the function with a closure around the old state variable. If you can, try doing useEffect thisDay in place of useEffectOnce, though that will mean that your checkDateChange will run at every state change even if it hasn’t been a second since the last time it ran. If you need it to run exactly every second, then maybe an EffectRef will do the trick.

Thank you for your answer @ntwilson. Your reply confirmed my suspicion about function closure.

Instead I add an ugly Ref as following:

    -- ...
    thisDay /\ setThisDay <- useState' (Nothing :: Maybe Date)
    thisDayRef <- useRef Nothing
    let resizeHandler _ = setWindowWidth <<< Just <<< toNumber =<< innerWidth =<< window
        checkDateChange = do
            today <- Just <$> nowDate
            thisDay <- readRef thisDayRef
            if today /= thisDay
                then do
                    Console.log $ i "thisDay=" (show thisDay) ", nowDate=" (show today)
                    setThisDay today
                    writeRef thisDayRef today
                else pure unit
    -- ...

This solved the problem but it is ugly to modify the same information in two different places.

Thank you for your help and time.

The pattern is explained in depth here.

There are myriad hook utility libs with the patterns implemented in even more sophisticated ways like useIntervalWhen or useInterval.

Hope it helps!

Could you use useState instead (without the tick) to get the current value of thisDay?

  let
    checkDateChange = do
      today <- Just <$> Now.nowDate
      setThisDay \thisDay' ->
        Debug.trace ("thisDay=" <> (show thisDay') <> ", nowDate=" <> (show today))
          \_ -> if today /= thisDay' then today else thisDay'

https://try.purescript.org/?gist=d87c919fc52ca5fb3f1dc9f4e0bd36e5

@codingedgar
I read the articles and indeed they are helpful (considering that I’m not a React savvy), thank you. But I don’t feel so experienced to do the type wrestling to port them to Purescript :disappointed:

@ptrfrncsmrph
Interesting! I shall try it tomorrow evening. It is night here. I have something to do all day tomorrow. I shall inform you about the result. Thanks for the advice.

@ptrfrncsmrph’s solution ( useState over useState') is the simplest correct way to solve this, I just assumed you discarded it to avoid re-renders.

As you mention you’re not React savvy: using setState can cause a lot of re-renders, but in general, it shouldn’t cause re-renders if the values are the same)

Just for completion from the react docs & javascript - React hooks useState setValue still rerender one more time when value is equal - Stack Overflow

If you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)

Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree. If you’re doing expensive calculations while rendering, you can optimize them with useMemo.

In this particular case, I think you’d be safe. I added the docs to avoid getting bitten later on, with more complex states that Object.is returns false for, thus causing a bunch of re-renders.


Also, yeah, I think porting those hooks with general types is too much for me too, I’m 100% noob with PureScript, but porting with an ad-hoc signature should be simpler, and that’s what I’ve been doing so far :sweat_smile:

Good luck :+1:

I’ve just thought that it would be easier to use set function of useState' without providing an extra function.:thinking: But actually the set function of the useState provides the needed current value to compare to.

I also (maybe correctly) thought that the comparison of the Date values by React might be false even though the values are the same because of the references might be different (I don’t know the memory representation of Dates in JavaScript). So tried not to call the setValue if values are the same.

Thank you for the explanation and the extra pointers.

2 Likes