Lift the feed's view-mode state (server-synced: persists on pick, adopted from prefs on login) out of App into FeedViewProvider. App reads it via useFeedView() to hand to Feed/ChannelPage; the view adopt lines leave App's big prefs-adopt effect. Removes another App-owned cross-cutting state ahead of the render flip. (Platform S6b.3)
70 lines
2.8 KiB
TypeScript
70 lines
2.8 KiB
TypeScript
import {
|
|
createContext,
|
|
useCallback,
|
|
useContext,
|
|
useEffect,
|
|
useMemo,
|
|
useState,
|
|
type ReactNode,
|
|
} from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { api } from "../lib/api";
|
|
import { parseFeedView, type FeedView } from "../lib/feedView";
|
|
import { LS, readAccount, writeAccount } from "../lib/storage";
|
|
|
|
// Owns the feed's view mode (the card/row/tile density switcher), lifted out of App so both the feed
|
|
// page and the channel page read it via hooks instead of prop-drilling `view`/`setView` through App.
|
|
// Split State + Actions contexts, mirroring the other providers; composed in main.tsx around <App/>.
|
|
//
|
|
// The view mode is an account PREFERENCE, not a filter: it persists to the server on pick (savePrefs)
|
|
// and is adopted from server prefs on login — so unlike the ephemeral list filters, this provider
|
|
// carries the same server-sync the App god component used to. A localStorage mirror seeds the initial
|
|
// render synchronously; the passive ["me"] observer adopts the server value once the account resolves.
|
|
|
|
interface FeedViewActions {
|
|
setView: (v: FeedView) => void;
|
|
}
|
|
|
|
const StateContext = createContext<FeedView>("cards");
|
|
const ActionsContext = createContext<FeedViewActions>({ setView: () => {} });
|
|
|
|
export function useFeedView(): FeedView {
|
|
return useContext(StateContext);
|
|
}
|
|
export function useFeedViewActions(): FeedViewActions {
|
|
return useContext(ActionsContext);
|
|
}
|
|
|
|
export function FeedViewProvider({ children }: { children: ReactNode }) {
|
|
const [view, setViewState] = useState<FeedView>(() => parseFeedView(readAccount(LS.feedView, null)));
|
|
// Passive observer of the account App fetches (enabled:false → reads cache, re-renders on update).
|
|
const { data: me } = useQuery({ queryKey: ["me"], queryFn: api.me, enabled: false });
|
|
|
|
// A user pick persists both to the localStorage mirror (instant reload) and to the server.
|
|
const setView = useCallback((next: FeedView) => {
|
|
setViewState(next);
|
|
writeAccount(LS.feedView, next);
|
|
api.savePrefs({ view: next }).catch(() => {});
|
|
}, []);
|
|
|
|
// Adopt the server-stored view on login / account switch. Writes the mirror but does NOT savePrefs
|
|
// (that would echo the just-adopted value straight back). parseFeedView also maps the pre-0.45
|
|
// "grid" / "list" values forward.
|
|
useEffect(() => {
|
|
const prefs = me?.preferences;
|
|
if (!prefs) return;
|
|
const adopted = parseFeedView(prefs.view);
|
|
setViewState(adopted);
|
|
writeAccount(LS.feedView, adopted);
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [me?.id]);
|
|
|
|
const actions = useMemo<FeedViewActions>(() => ({ setView }), [setView]);
|
|
|
|
return (
|
|
<ActionsContext.Provider value={actions}>
|
|
<StateContext.Provider value={view}>{children}</StateContext.Provider>
|
|
</ActionsContext.Provider>
|
|
);
|
|
}
|