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
@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
Good luck
I’ve just thought that it would be easier to use set function of useState'
without providing an extra function. 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 Date
s 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