fix(channels): re-hydrate per-account manager state once the account is pinned
The provider tree mounts above <App>, which pins this tab's account only after /api/me resolves — so on a genuinely fresh tab (empty sessionStorage) the manager's sort, filters, view and layout initialised from an unknown account and stayed at defaults until a second reload. Make the active account reactive (a subscribable store + useActiveAccountId via useSyncExternalStore; setActiveAccount/clearActiveAccount notify) and re-read the stored value when the account transitions null→id, via a shared useAccountRehydrate hook. A markDirty guard (called by each owning setter) prevents clobbering a value the user changed in the brief pre-pin window; the effect is keyed on the account id only, so the user's own writes can't loop it. useSortState, useFilterState and ChannelsProvider (view/layout) all adopt it. Affects sort identically — this was a long-standing caveat, not new to R13.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
import { useActiveAccountId } from "./storage";
|
||||
|
||||
// Re-read per-account persisted state when this tab's active account becomes known (or changes). The
|
||||
// provider tree above <App> mounts before the account is pinned, so its useState initializers seed from
|
||||
// an unknown account (empty defaults); once <App> pins it, `rehydrate` runs to load the real stored
|
||||
// value. Guarded so it never clobbers a value the user set in the brief pre-pin window: the returned
|
||||
// `markDirty` is called by the owning setter, and a dirty state skips the re-hydrate.
|
||||
//
|
||||
// Keyed on the account id ONLY — the user's own writes go through setAccountRaw, which does not change
|
||||
// the account id, so this effect can't loop on them.
|
||||
export function useAccountRehydrate(rehydrate: () => void): () => void {
|
||||
const accountId = useActiveAccountId();
|
||||
const dirty = useRef(false);
|
||||
const prev = useRef(accountId);
|
||||
// Latest closure in a ref so the effect depends on the account id alone (a fresh `rehydrate` each
|
||||
// render must not re-fire it), while still calling the current logic.
|
||||
const fn = useRef(rehydrate);
|
||||
fn.current = rehydrate;
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId === prev.current) return; // mount, and renders with no real transition
|
||||
prev.current = accountId;
|
||||
if (!dirty.current) fn.current();
|
||||
}, [accountId]);
|
||||
|
||||
return useCallback(() => {
|
||||
dirty.current = true;
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user