5.1 KiB
State Management In-Depth
This document is a granulated shard from the main "5-front-end-architecture.md" focusing on "State Management In-Depth".
This section expands on the State Management strategy chosen (Zustand) and outlined in the "Overall Frontend Philosophy & Patterns".
- Chosen Solution: Zustand (Latest version, as per
architecture.txt) - Rationale: Zustand was chosen for its simplicity, small bundle size, and unopinionated nature, suitable for BMad DiCaster's relatively simple frontend state needs (e.g., podcast player status). Server-side data is primarily managed by Next.js Server Components.
Store Structure / Slices
Global client-side state will be organized into distinct "slices" within store/slices/. Components can import and use individual stores directly.
-
Conventions:
- Each slice in its own file:
store/slices/camelCaseSlice.ts. - Define state interface, initial state, and action functions.
- Each slice in its own file:
-
Core Slice:
podcastPlayerSlice.ts(for MVP)-
Purpose: Manages the state of the podcast player (current track, playback status, time, volume).
-
Source File:
store/slices/podcastPlayerSlice.ts -
State Shape (Example):
interface PodcastTrack { id: string; // Could be newsletterId or a specific audio ID title: string; audioUrl: string; duration?: number; // in seconds } interface PodcastPlayerState { currentTrack: PodcastTrack | null; isPlaying: boolean; currentTime: number; // in seconds volume: number; // 0 to 1 isLoading: boolean; error: string | null; } interface PodcastPlayerActions { loadTrack: (track: PodcastTrack) => void; play: () => void; pause: () => void; setCurrentTime: (time: number) => void; setVolume: (volume: number) => void; setError: (message: string | null) => void; resetPlayer: () => void; } -
Key Actions:
loadTrack,play,pause,setCurrentTime,setVolume,setError,resetPlayer. -
Zustand Store Definition:
import { create } from "zustand"; // Previously defined interfaces: PodcastTrack, PodcastPlayerState, PodcastPlayerActions const initialPodcastPlayerState: PodcastPlayerState = { currentTrack: null, isPlaying: false, currentTime: 0, volume: 0.75, isLoading: false, error: null, }; export const usePodcastPlayerStore = create< PodcastPlayerState & PodcastPlayerActions >((set) => ({ ...initialPodcastPlayerState, loadTrack: (track) => set({ currentTrack: track, isLoading: true, // Assume loading until actual audio element confirms error: null, isPlaying: false, // Usually don't autoplay on load currentTime: 0, }), play: () => set((state) => { if (!state.currentTrack) return {}; // No track loaded return { isPlaying: true, isLoading: false, error: null }; }), pause: () => set({ isPlaying: false }), setCurrentTime: (time) => set({ currentTime: time }), setVolume: (volume) => set({ volume: Math.max(0, Math.min(1, volume)) }), setError: (message) => set({ error: message, isLoading: false, isPlaying: false }), resetPlayer: () => set({ ...initialPodcastPlayerState }), }));
-
Key Selectors
Selectors are functions that derive data from the store state. With Zustand, state is typically accessed directly from the hook, but memoized selectors can be created with libraries like reselect if complex derived data is needed, though for simple cases direct access is fine.
- Convention: For direct state access, components will use:
const { currentTrack, isPlaying, play } = usePodcastPlayerStore(); - Example Selectors (if using
reselector similar, for more complex derivations later):selectCurrentTrackTitle: Returnsstate.currentTrack?.title || 'No track loaded'.selectIsPodcastPlaying: Returnsstate.isPlaying.
Key Actions / Reducers / Thunks
Zustand actions are functions defined within the create call that use set to update state. Asynchronous operations (like fetching data, though less common for Zustand which is often for UI state) can be handled by calling async functions within these actions and then calling set upon completion.
- Convention: Actions are part of the store hook:
const { loadTrack } = usePodcastPlayerStore();. - Asynchronous Example (Conceptual, if a slice needed to fetch data):
For BMad DiCaster MVP, most data fetching is via Server Components. Client-side async actions in Zustand would primarily be for client-specific operations not directly tied to server data fetching.
// In a hypothetical userSettingsSlice.ts // fetchUserSettings: async () => { // set({ isLoading: true }); // try { // const settings = await api.fetchUserSettings(); // api is an imported service // set({ userSettings: settings, isLoading: false }); // } catch (error) { // set({ error: 'Failed to fetch settings', isLoading: false }); // } // }