-
Notifications
You must be signed in to change notification settings - Fork 432
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: WebSocket is closed before the connection is established
warning
#8042
base: next
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
1 Skipped Deployment
|
No changes to documentation |
Component Testing Report Updated Dec 13, 2024 4:31 PM (UTC) ❌ Failed Tests (6) -- expand for details
|
⚡️ Editor Performance ReportUpdated Fri, 13 Dec 2024 16:32:20 GMT
Detailed information🏠 Reference resultThe performance result of
🧪 Experiment resultThe performance result of this branch
📚 Glossary
|
7705d14
to
8d2dc46
Compare
@@ -16,7 +16,7 @@ export interface ResourceCacheProviderProps { | |||
|
|||
/** @internal */ | |||
export function ResourceCacheProvider({children}: ResourceCacheProviderProps) { | |||
const resourceCache = useMemo((): ResourceCache => { | |||
const [resourceCache] = useState((): ResourceCache => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A great rule of thumb for determining wether a useMemo
is safe and side-effects free is wether you can remove it and what happens to your app:
- a) it still works, but slower
- b) it breaks, or is so much slower it's unusable
In this case it's b), as the assumption here is that useMemo
with zero dependencies means resourceCache
will run exactly once, and have a stable instance for hte lifecycle of components calling it. This is false. In fact, it can choose to throw away the memoized value in certain suspense scenarios: https://react.dev/reference/react/useMemo#caveats
A better solution is to use useState
, as it does have a guarantee that it's never throwing away the value, and it has a lazy init function so that our function that creates the map is still only run once.
This is also reflected in how much simpler the React Compiler optimization is for useState
, compared to the useMemo
.
Description
Ever since I gave up on #7227 it's been in the back of my mind, and taunting me every time I've been in a studio and seen this warning:
That ends today, I've finally found a stable workaround!
At some point we should dive into
@sanity/bifur-client
and find out exactly why the observable teardown callingws.close
causes the warning, maybe we have to checkws.readyState
? Maybe we can't avoid this warning if the observable is unsubscribed to before the connection is established, and while the warning is annoying it remains the right thing to do.Or maybe we should wait a tick
Promise.resolve().then
before creating the WebSocket, in case react does a new render asuserStore
changed so we avoid creating a resource that will be immediately closed anyway (we use this technique inreact-rx
, as well as in@sanity/client
for itsclient.listen()
observable).I don't know, I'm not an WebSocket expert.
What I do know is that in #7227 the regression that lead to the WebSocket warning showing 3 times instead of just once, was because it shifted
useGlobalPresence
anduseDocumentPresence
completely away fromuseEffect
touseObservable
fromreact-rx
.The benefits of
react-rx
'suseObservable
are many:useSyncExternalStore
.rxjs
, in a way that works with React 18 and 19 semantics and concurrent modes.But, since
useObservable
is designed to allow synchronous emits, it warms up the observable you give it, and subscribes to it during render.Previously
useGlobalPresence
anduseDocumentPresence
subscribed much later, in auseEffect
, causing the bifur websocket to be setup after react is done with initial renders.The reason why the issue is resolved this time around is because the observables aren't subscribed to until after an initial
useEffect
runs and tracks mounted state. By the time this happens the dependencies forusePresenceStore
(bifur
,connectionStatusStore
, anduserStore
) have stabilized and we no longer see eager closing of the WebSocket.What to review
Hopefully this makes sense 🙌
Testing
I've tested manually with
pnpm dev
, as well as the preview deployment and I've verified that the warning no longer happens.I've also verified on network devtools that there's only one websocket resource related to bifur.
Here's the before screenshot, that shows the eagerly closed resource with its
Finished
status, in addition to the live one with status 101, and withTime
set toPending
(indicating it's open and active):And here's the after, showing only a single resource:
Notes for release