[POC] useOnyx subscribed#808
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9e6e74e41e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b8bcea76e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 52c7e8f3ce
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e12bd8663b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13f825e360
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c034d60402
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| useEffect(() => { | ||
| subscribedRef.current = subscribed; |
There was a problem hiding this comment.
Sync subscribed ref before passive effects
When a consumer flips subscribed from true to false (for example during a screen-blur render), this passive effect leaves subscribedRef.current at the old true value until after commit. Any Onyx write from a sibling layout effect or immediate microtask in that commit→effect window will pass the guard in the connection callback and call onStoreChange(), causing the off-screen/background re-render this option is meant to suppress. Syncing the ref during render or in a layout effect avoids that stale-true window.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
You're right that there's a small gap. When subscribed flips from true to false, the ref only updates after React runs effects. If an Onyx write lands in that tiny window between the render committing and the effect running, the callback still sees true and triggers one re-render that ideally would have been skipped.
We're keeping it this way on purpose, for three reasons:
-
First, the damage is tiny and harmless. Worst case is one extra render, showing correct data, on a screen that just went off-screen. Compare that to the opposite direction (false to true): if the ref were stale there, a visible screen would miss fresh data, which is a real correctness bug. That direction is already handled by the catch-up in this effect. So the rule of thumb here is: when the gate is briefly wrong, it should fail by rendering too much, never by showing stale data. This window fails in the safe direction.
-
Second, the suggested fixes are worse than the problem. We can't "catch up" after the fact like we did for the focus direction, because you can't undo a render that already happened. The only real fix is updating the ref earlier. Updating it during render was already flagged as a P2 in an earlier round: with concurrent rendering, an aborted render could write false into the ref while the visible screen is still subscribed, and then a visible screen stops getting updates. useLayoutEffect would work but brings the SSR warning problem and would be the first use of that pattern in this library. Neither trade is worth it to skip one background render in case of that tiny gap.
-
Third, TanStack Query has exactly the same window in its subscribed option and ships with it unmitigated. Their subscription is only torn down when React runs the passive effect, so a query update in that same commit-to-effect gap also re-renders the component that just unsubscribed. This isn't an implementation bug on our side, it's just how useSyncExternalStore works: subscription changes always take effect when effects flush, not when the render commits.
There was a problem hiding this comment.
Yeah I think it's not worth investing on this, bringing more unecessary complexity is exactly what we want to avoid in the hook
Details
useOnyxnow accepts a subscribed option (defaults to true)When
subscribedisfalse, the hook keeps its Onyx connection open (so the value stays cache-warm) but stops re-rendering the component on background writes to that key. It defers the render trigger, not the value: any render from another cause still reads the latest value viagetSnapshot(), and flipping subscribed back to true (e.g. on re-focus) re-renders with the newest value.Why
Out-of-focus screens re-render every time their underlying data churns, even when nothing is on screen. That's wasted render work. subscribed: false lets a screen pause its render reactions while staying connected, then catch up instantly when it matters again, without paying the connect/disconnect cost of tearing the subscription down and rebuilding it.
Why this implementation
onStoreChange()behind a ref, don't re-subscribe. The connect callback reads subscribedRef.current and skipsonStoreChange()while paused. Toggling subscribed never re-subscribes, so we avoid the connect/reset churn a subscribe-dependency approach would cause.shouldGetCachedValueRef, the next render from any cause serves the fresh value instead of a cached-stale one.getSnapshot()returns the same reference anduseSyncExternalStorebails out).Related Issues
GH_LINK
Linked E/App PR
Automated Tests
Manual Tests
Author Checklist
### Related Issuessection above### Linked E/App PRsection above, and verified this change against it (E/App CI passed and manual testing completed)TestssectiontoggleReportand notonIconClick)myBool && <MyComponent />.STYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)/** comment above it */thisproperly so there are no scoping issues (i.e. foronClick={this.submit}the methodthis.submitshould be bound tothisin the constructor)thisare necessary to be bound (i.e. avoidthis.submit = this.submit.bind(this);ifthis.submitis never passed to a component event handler likeonClick)Avataris modified, I verified thatAvataris working as expected in all cases)mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari