/** * Store Selectors * FE-STATE-008: Optimize state selectors to prevent unnecessary re-renders * * Provides optimized selectors for Zustand stores to prevent unnecessary re-renders. * Use these selectors instead of accessing the entire store. */ import { useShallow } from 'zustand/react/shallow'; import { useQueryClient } from '@tanstack/react-query'; import { useAuthStore } from '@/features/auth/store/authStore'; import { useUIStore, type UIStore } from '@/stores/ui'; import { useLibraryStore, type LibraryStore } from '@/stores/library'; import { useChatStore, type ChatStore } from '@/stores/chat'; import { useLibraryItems as useLibraryItemsQuery, useLibraryFavorites as useLibraryFavoritesQuery, libraryQueryKeys, type LibraryItemsParams, } from '@/features/library/hooks/useLibraryItems'; import type { User, LibraryItem, ChatMessage, Conversation } from '@/types'; import type { ApiError } from '@/types/api'; /** * FE-STATE-008: Optimized selectors for AuthStore * * These hooks only re-render when the selected values actually change. */ export function useAuthUser(): User | null { return useAuthStore(useShallow((state) => state.user)); } export function useAuthStatus(): { isAuthenticated: boolean; isLoading: boolean; error: ApiError | null; } { return useAuthStore( useShallow((state) => ({ isAuthenticated: state.isAuthenticated, isLoading: state.isLoading, error: state.error, })), ); } export function useAuthActions() { return useAuthStore( useShallow((state) => ({ login: state.login, register: state.register, logout: state.logout, refreshUser: state.refreshUser, checkAuthStatus: state.checkAuthStatus, clearError: state.clearError, setLoading: state.setLoading, })), ); } /** * FE-STATE-008: Optimized selectors for UIStore */ export function useUITheme(): 'light' | 'dark' | 'system' { return useUIStore(useShallow((state: UIStore) => state.theme)); } export function useUILanguage(): 'en' | 'fr' { return useUIStore(useShallow((state: UIStore) => state.language)); } export function useUISidebar() { return useUIStore( useShallow((state: UIStore) => ({ sidebarOpen: state.sidebarOpen, setSidebarOpen: state.setSidebarOpen, })), ); } export function useUINotifications() { return useUIStore( useShallow((state: UIStore) => ({ notifications: state.notifications, addNotification: state.addNotification, removeNotification: state.removeNotification, markNotificationAsRead: state.markNotificationAsRead, clearNotifications: state.clearNotifications, })), ); } export function useUIActions() { return useUIStore( useShallow((state: UIStore) => ({ setTheme: state.setTheme, setLanguage: state.setLanguage, setSidebarOpen: state.setSidebarOpen, })), ); } /** * FE-STATE-008: Optimized selectors for LibraryStore * FE-STATE-009: Convert normalized state to arrays for compatibility * * NOTE: These selectors now use React Query hooks instead of Zustand store. * They maintain the same interface for backward compatibility. */ export function useLibraryItems( params: LibraryItemsParams = {}, ): LibraryItem[] { const { data } = useLibraryItemsQuery(params); return data?.items ?? []; } export function useLibraryFavorites(): LibraryItem[] { const { data } = useLibraryFavoritesQuery(); return data ?? []; } /** * FE-STATE-009: Get normalized state directly (for advanced use cases) */ export function useLibraryItemsNormalized() { return useLibraryStore(useShallow((state: LibraryStore) => state.items)); } export function useLibraryFavoritesNormalized() { return useLibraryStore(useShallow((state: LibraryStore) => state.favorites)); } export function useLibraryFilters() { return useLibraryStore( useShallow((state: LibraryStore) => ({ filters: state.filters, setFilters: state.setFilters, })), ); } export function useLibraryPagination(params: LibraryItemsParams = {}) { const { data } = useLibraryItemsQuery(params); return { page: data?.page ?? 1, limit: data?.limit ?? 20, total: data?.total ?? 0, has_next: data?.has_next ?? false, has_prev: data?.has_prev ?? false, }; } export function useLibraryStatus(): { isLoading: boolean; error: ApiError | null; } { const itemsQuery = useLibraryItemsQuery({}); const favoritesQuery = useLibraryFavoritesQuery(); return { isLoading: itemsQuery.isLoading || favoritesQuery.isLoading, error: (itemsQuery.error || favoritesQuery.error) as ApiError | null, }; } export function useLibraryActions() { const queryClient = useQueryClient(); const libraryStore = useLibraryStore(); return { fetchItems: async (params?: LibraryItemsParams) => { await queryClient.refetchQueries({ queryKey: libraryQueryKeys.items(params), }); }, fetchFavorites: async () => { await queryClient.refetchQueries({ queryKey: libraryQueryKeys.favorites(), }); }, uploadFile: async ( file: File, metadata: { title: string; description?: string }, ) => { // TODO: Migrate to React Query mutation with optimistic update // For now, call API and invalidate queries const { apiClient } = await import('@/services/api/client'); const formData = new FormData(); formData.append('file', file); formData.append('title', metadata.title); if (metadata.description) { formData.append('description', metadata.description); } await apiClient.post('/tracks', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); await queryClient.invalidateQueries({ queryKey: libraryQueryKeys.all }); }, toggleFavorite: async (itemId: string) => { // TODO: Migrate to React Query mutation with optimistic update // For now, call API and invalidate queries const { apiClient } = await import('@/services/api/client'); await apiClient.post(`/tracks/${itemId}/favorite`); await queryClient.invalidateQueries({ queryKey: libraryQueryKeys.all }); }, deleteItem: async (itemId: string) => { // TODO: Migrate to React Query mutation with optimistic update // For now, call API and invalidate queries const { apiClient } = await import('@/services/api/client'); await apiClient.delete(`/tracks/${itemId}`); await queryClient.invalidateQueries({ queryKey: libraryQueryKeys.all }); }, clearItems: () => { // Invalidate all library queries instead of clearing store queryClient.invalidateQueries({ queryKey: libraryQueryKeys.all }); }, }; } /** * FE-STATE-008: Optimized selectors for ChatStore */ export function useChatConversations(): Conversation[] { return useChatStore(useShallow((state: ChatStore) => state.conversations)); } export function useChatCurrentConversation(): Conversation | null { return useChatStore( useShallow((state: ChatStore) => state.currentConversation), ); } export function useChatMessages(conversationId: string): ChatMessage[] { return useChatStore( useShallow((state: ChatStore) => state.messages[conversationId] || []), ); } export function useChatTypingUsers(conversationId: string): string[] { return useChatStore( useShallow((state: ChatStore) => state.typingUsers[conversationId] || []), ); } export function useChatConnection(): { isConnected: boolean; isLoading: boolean; error: string | null; } { return useChatStore( useShallow((state: ChatStore) => ({ isConnected: state.isConnected, isLoading: state.isLoading, error: state.error, })), ); } export function useChatActions() { return useChatStore( useShallow((state: ChatStore) => ({ setConversations: state.setConversations, setCurrentConversation: state.setCurrentConversation, addMessage: state.addMessage, updateMessage: state.updateMessage, removeMessage: state.removeMessage, setMessages: state.setMessages, setTypingUsers: state.setTypingUsers, addTypingUser: state.addTypingUser, removeTypingUser: state.removeTypingUser, setConnected: state.setConnected, setLoading: state.setLoading, setError: state.setError, connect: state.connect, disconnect: state.disconnect, joinConversation: state.joinConversation, leaveConversation: state.leaveConversation, sendMessage: state.sendMessage, startTyping: state.startTyping, stopTyping: state.stopTyping, addReaction: state.addReaction, removeReaction: state.removeReaction, fetchConversations: state.fetchConversations, createConversation: state.createConversation, })), ); } /** * FE-STATE-008: Helper function to create custom selectors * * Use this when you need to select multiple values from a store. * * @example * ```typescript * // Instead of: * const { user, isAuthenticated } = useAuthStore(); * * // Use: * const { user, isAuthenticated } = useStoreSelector(useAuthStore, (state) => ({ * user: state.user, * isAuthenticated: state.isAuthenticated, * })); * ``` */ export function useStoreSelector( store: (selector: (state: T) => U) => U, selector: (state: T) => U, ): U { return store(useShallow(selector)); }