Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: observer class components going stale after 10 seconds #3776

Merged
merged 2 commits into from
Oct 29, 2023

Conversation

wbercx
Copy link
Contributor

@wbercx wbercx commented Oct 19, 2023

After upgrading from MobX 4 to MobX 6 in a React Native project (React 18.2, no StrictMode), I noticed almost all of my screens went stale shortly after rendering.

An observer class component is registered with observerFinalizationRegistry like so:

_observerFinalizationRegistry.register(this, admin, this);

The third argument is the token, which is used to store the registration in a Map. In componentDidMount the finalization is then unregistered so it is allowed to stay alive, but using admin as the token, not this:

_observerFinalizationRegistry.unregister(admin);

admin is not registered to begin with, so the finalization does not get cleaned up, resulting in every class component going stale after about 10 seconds of being rendered.

Code change checklist

  • Added/updated unit tests
  • Updated /docs. For new functionality, at least API.md should be updated
  • Verified that there is no significant performance drop (yarn mobx test:performance)

Happy to write tests for this - but I will need some pointers. I saw the finalizationRegistry test, but it is not immediately obvious to me how I should approach testing registration and deregistration.

…s registered with `this` as the token, not `admin`.
@changeset-bot
Copy link

changeset-bot bot commented Oct 19, 2023

🦋 Changeset detected

Latest commit: 0099c75

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
mobx-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@urugator
Copy link
Collaborator

urugator commented Oct 21, 2023

Damit. Thank you! LGTM

Test is not mandatory, but ppbly something like following:

test("should unregister from FinalizationRegistry once commited #3776", async () => {
    expect(typeof globalThis.FinalizationRegistry).toBe("function")

    const o = mobx.observable({ x: 0 })
    let strongRef = {};
    const weakRef = new WeakRef(strongRef);

    @observer
    class TestCmp extends React.Component<any> {
        render() {
            return o.x
        }
    }

    const { unmount, container } = render(<TestCmp />)

    expect(container).toHaveTextContent("0")
     
    // Wait for GC to kick in
    strongRef = undefined;
    await Promise.resolve()
    gc()
    await waitFor(() => expect(weakRef!.deref()).toBeUndefined(), {
        timeout: 10_000,
        interval: 150
    })
    // GC did run, component is mounted and should remain reactive
    act(() => o.x++);
    expect(container).toHaveTextContent("1")

    unmount()
})

@urugator urugator merged commit 5063c38 into mobxjs:main Oct 29, 2023
1 check passed
@github-actions github-actions bot mentioned this pull request Oct 29, 2023
@urugator urugator mentioned this pull request Oct 29, 2023
@urugator
Copy link
Collaborator

The above doesn't work, because the strongRef is never reclaimed by GC and even if it would, it wouldn't necessarily mean that the component instance is reclaimed as well. We can force finalization with our TimerBasedFinalizationRegistry, so I wrote a test using these: #3787

urugator added a commit that referenced this pull request Oct 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants