refactor(playlists): scope module state in PlaylistsProvider
Lift the Playlists module's shared local state (rail name filter + selected playlist) out of App into a split-context PlaylistsProvider, mirroring FeedFiltersProvider. Header, PlaylistsRail, and the Playlists detail pane read it via usePlaylists()/usePlaylistsActions() instead of props drilled through App; the modules take only a uniform `me` prop now. App loses two useStates and the selected-playlist persistence helper. (Platform S6b.2)
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { getAccountRaw, LS, setAccountRaw } from "../lib/storage";
|
||||
|
||||
// Owns the Playlists module's shared local state — the rail's name filter and the selected playlist
|
||||
// — lifted out of the App god component so the header search box, the PlaylistsRail, and the
|
||||
// Playlists detail pane read it via hooks instead of prop-drilling through App. Mirrors
|
||||
// FeedFiltersProvider / NavigationProvider (split State + Actions contexts, one-line hooks; composed
|
||||
// in main.tsx around <App/>).
|
||||
//
|
||||
// The `search` term is ephemeral (resets on reload, like the other list filters); the selected
|
||||
// playlist is per-account persisted (LS.playlist) so a reload returns you to it. Neither is
|
||||
// server-synced, and an account switch does a full reload, so init-from-storage is all the
|
||||
// hydration these need — no per-account load effect (unlike the server-backed feed filters).
|
||||
|
||||
type PlaylistsState = {
|
||||
search: string;
|
||||
selectedId: number | null;
|
||||
};
|
||||
|
||||
interface PlaylistsActions {
|
||||
setSearch: (q: string) => void;
|
||||
setSelectedId: (id: number | null) => void;
|
||||
}
|
||||
|
||||
const StateContext = createContext<PlaylistsState>({ search: "", selectedId: null });
|
||||
const ActionsContext = createContext<PlaylistsActions>({
|
||||
setSearch: () => {},
|
||||
setSelectedId: () => {},
|
||||
});
|
||||
|
||||
export function usePlaylists(): PlaylistsState {
|
||||
return useContext(StateContext);
|
||||
}
|
||||
export function usePlaylistsActions(): PlaylistsActions {
|
||||
return useContext(ActionsContext);
|
||||
}
|
||||
|
||||
export function PlaylistsProvider({ children }: { children: ReactNode }) {
|
||||
const [search, setSearchState] = useState("");
|
||||
const [selectedId, setSelectedIdState] = useState<number | null>(() => {
|
||||
const s = getAccountRaw(LS.playlist);
|
||||
return s ? Number(s) : null;
|
||||
});
|
||||
|
||||
const setSearch = useCallback((q: string) => setSearchState(q), []);
|
||||
const setSelectedId = useCallback((id: number | null) => {
|
||||
setSelectedIdState(id);
|
||||
if (id != null) setAccountRaw(LS.playlist, String(id));
|
||||
}, []);
|
||||
|
||||
const state = useMemo<PlaylistsState>(() => ({ search, selectedId }), [search, selectedId]);
|
||||
const actions = useMemo<PlaylistsActions>(
|
||||
() => ({ setSearch, setSelectedId }),
|
||||
[setSearch, setSelectedId],
|
||||
);
|
||||
|
||||
return (
|
||||
<ActionsContext.Provider value={actions}>
|
||||
<StateContext.Provider value={state}>{children}</StateContext.Provider>
|
||||
</ActionsContext.Provider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user