[WIP] BUG: 'Long-lived' setState function fails to persist updates#44
[WIP] BUG: 'Long-lived' setState function fails to persist updates#44bryphe wants to merge 2 commits into
Conversation
|
@bryphe I wonder how this issue and the internal implementation of state would be affected by the idea discussed in #39 (comment). If we pass the Another idea before trying a refactor is to move the
Maybe there are ways to process instance state in a "non-global way", so we can avoid the indirection of keeping instance IDs and a table to store them. I'm a bit concerned about adding IDs to the instances and a new table because I've had painful experiences in the past due to solutions I designed that way and the implications they carried: a new class of situations with "orphan IDs" without a backing value did emerge, the 1:1 relationship between elements and instances could potentially be broken, and in general it involves more work to keep those IDs and the contents of the table updated. I would like to understand better why this problem is happening. Thanks for creating that test!! I have to take a closer look at it. 🙂 This is just an early sketch of an alternate idea, and it is probably out of scope 😅 But, considering right now "elements" in Instead of elements storing a reason-reactify/lib/Reactify.re Lines 8 to 13 in d3271aa We could rather have this internal So, things like For that to happen, we would probably need to make module CounterButtons = (
val component((render, ~children, (), {useState, useContext}) => render(() => {
</view> /* Use useState and useContext here */
}, ~children))
);That way, the calls to I hope that makes sense! There are many unclear parts, and things that could be improved (like the orphan unit in the |
Issue: It seems that there is a 'state persistence' issue - state updated higher in the hierarchy can pave state lower in the hierarchy, when state lower in the hierarchy is set via a 'long-lived' setState handler. I hit this issue when I was working on the
sliderprototype control in Revery.It was a little tricky to reproduce in isolation, but I believe there are a couple of conditions required to hit this bug:
setStatetype function acquired fromuseState. This could take a couple forms - auseEffectthat setscondition=Effects.MountUnmount(so it is only fired in the initial mount), or an event handler like in theMouse.setCapturecase. The initialmousedownsuccessfully triggers a state update, but subsequent state updates called during themousemoveevents are ignored.revery, this happens due to the UI update that occurs in therenderfunction, or when an ancestor component calls a function in response to an event from a lower component (ie, a component listening toonValueChangedfrom a slider).I created a test case so far in the PR to 'exercise' this case - I believe if we can get the test case fixed, it'll also address the bugs I saw with the slider functionality. The new test runs the following in sequence:
We'd expect at the end that the inner component would have state 6, but it actually reverts back to its original value of 2 after the last outer component update.
Defect: My understanding so far is that, when code 'hangs on' to a
setStatefunction provided byuseState, in the scenario outlined above - what happens is that when the handler down the road calls the oldsetState, it will update a detached instance that isn't up-to-date.Potential Fixes: I think the crux of the issue is here:
In that the
currentContextis out-of-date in this particular case.There is some awkwardness in how we manage the state today:
(this concept is overloaded - we use the
contextto store the entire component instance, and then there are these separate fields looking at currentState/newState - it makes it hard to understand the chain of operations here).I'm considering a refactor where we give each instance a unique instance ID. Then, instead of currying the entire context to
dispatch, we could dispatch this ID, which should persist across re-renders as long as the instance is still available. Then, the state management becomes a hash table ofuniqueId->instance, and managing our hierarchy of instances means managing a tree ofuniqueId. I believe this could simplify the state management of instances while addressing the bug exposed by the test case - thesetStatecould never refer to an old node unless it had been removed from the tree (in which case we could detect it and log a warning).