MSD: Refactor interim omnibar to hooks-based data flow#109881
MSD: Refactor interim omnibar to hooks-based data flow#109881
Conversation
Jetpack Cloud Live (direct link)
Automattic for Agencies Live (direct link)
Dashboard Live (dotcom) (direct link)
|
client/dashboard/app/interim-omnibar/test/use-interim-omnibar-data.test.tsx
Outdated
Show resolved
Hide resolved
fushar
left a comment
There was a problem hiding this comment.
Other than that I believe this is good 👍 Tested.
client/dashboard/app/interim-omnibar/test/use-interim-omnibar-data.test.tsx
Outdated
Show resolved
Hide resolved
97c98cf to
b03137c
Compare
p-jackson
left a comment
There was a problem hiding this comment.
I thought this might produce errors because the <InterimOmnibar> rendered on the server at client/document/index.jsx doesn't have the container and the client-side one does. I thought that's the sort of thing that breaks hydration.
Evidently not.
A nit is that I would perhaps combine the container and hook into a single tsx file, since they really only work together.
|
|
||
| const { data: site = null } = useQuery( { | ||
| ...siteByIdQuery( siteId ?? 0 ), | ||
| enabled: hydrated && !! siteId, |
There was a problem hiding this comment.
I think we need to add a isRecentSitesLoaded check here too. Otherwise the omnibar could flash the primary blog details before switching to the recent site details.
The problem is that the siteByIdQuery is going to start up as soon as there is any site ID available, but we really want it to wait for the preferences to finish loading first.
There was a problem hiding this comment.
I was meaning to fix this behavior in my next PR 😄 it's related to the origin_site_id check, where it trumps everything.
There was a problem hiding this comment.
Yeah definitely related to that!
| currentRoute: window.location.pathname, | ||
| }; |
There was a problem hiding this comment.
We should return the callbacks in this case too. It's a small thing, but the <InterimOmnibar> rebuilds the redux store whenever this callback changes, so we don't need these callbacks to unnecessarily change.
| currentRoute: window.location.pathname, | |
| }; | |
| currentRoute: window.location.pathname, | |
| onToggleMenu, | |
| onToggleNotifications, | |
| }; |
There was a problem hiding this comment.
Which means we should fix up the hook's jsdoc too.
There was a problem hiding this comment.
I don't understand, why didn't we also do this (returning callbacks pre-hydration) before this PR?
There was a problem hiding this comment.
In the .render() call was always passing the callback props. The hydrateRoot call wasn't, but that was my mistake. I suspect that between hydrateRoot and .render is was probably tearing down all the elements any, and so it was re-creating a store then no matter what.
client/dashboard/app/interim-omnibar/test/use-interim-omnibar-data.test.tsx
Outdated
Show resolved
Hide resolved
b03137c to
bf4d95a
Compare
The previous implementation drove React imperatively via `root.render` and `QueryObserver.subscribe` after `hydrateRoot`, which raced hydration and caused React to bail out with "This root received an early update, before anything was able to hydrate. Switched the entire root to client rendering." Move the data fetching into a new `InterimOmnibarContainer` that uses TanStack Query hooks (`useQuery` for auth, recentSites, and the active site). A `useState(false)` + `useEffect` gate ensures the first render mirrors the SSR output exactly (`user = initialUser`, `site = null`, no toggle callbacks); after hydration commits, the container switches to hook-driven data and wires up the toggle handlers. All post-hydration updates now flow through normal React rendering instead of external imperative calls. `loadOmnibar` shrinks to just click delegation and a single `hydrateRoot` call. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract the data-flow logic into an exported `useInterimOmnibarData` hook so it can be tested in isolation without rendering the full masterbar tree. Add unit tests covering: bootstrapped user via initialData, toggle callback wiring, recent-site loading, primary_blog fallback, and the null-site state when no siteId is known. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the weak assertions (`initialData` pass-through and the `site === null` default) with a `renderToStaticMarkup`-based test that verifies the hook's pre-hydration output matches the SSR shape exactly (user, null site, no callbacks). This is the contract hydration actually depends on. Drop the "keeps site null" test — it only asserted a destructuring default and would have passed even if the enabled guard was removed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move the hook out of `interim-omnibar-container.tsx` into `use-interim-omnibar-data.ts`, matching the source-file naming convention used by `hooks/use-persistent-view.ts` (and lining up with the test file name). Tests now build a fresh `OmnibarEvents` per test instead of subscribing to the shared module singleton, so a failed assertion can't leak a dangling subscription into later tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This reverts commit 84a0480.
Avoids nesting a second provider inside InterimOmnibar's own.
bf4d95a to
9cf950d
Compare
p-jackson
left a comment
There was a problem hiding this comment.
I've tested it with wpcom-bootstrap-user too to make sure there's no errors still.
Finally no race conditions in our hydration I think 🤞🤞🤞🤞
| const { data: site = null } = useQuery( | ||
| { | ||
| ...siteByIdQuery( siteId ?? 0 ), | ||
| enabled: hydrated && !! siteId && ! isRecentSitesLoading, |
There was a problem hiding this comment.
It seems this means the queries can't run in parallel. In any case, I'll try to fix it up later.
There was a problem hiding this comment.
Yes Claude was mad at me for breaking the parallel requests. But I think following it up once we have the final ordering of site IDs makes sense.
|
🎉 |
Proposed Changes
loadOmnibarto mount a hooks-based container (useInterimOmnibarData) instead of driving React imperatively withroot.renderandQueryObserver.subscribeafterhydrateRoot.Why are these changes being made?
The previous implementation raced hydration and caused React to bail with:
With the refactor, every post-hydration update flows through normal React rendering — there is no external code path that can update the root between
hydrateRootand its commit.Testing Instructions
dashboard/omnibarfeature flag.Pre-merge Checklist