/** * 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. */ // Avoid zustand/react/shallow to prevent React.Children init issues import { useQueryClient } from '@tanstack/react-query'; import { useAuthStore } from '@/features/auth/store/authStore'; import { useUser } from '@/features/auth/hooks/useUser'; 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 '@/schemas/apiSchemas'; /** * FE-STATE-008: Optimized selectors for AuthStore * * These hooks only re-render when the selected values actually change. * * Action 4.1.1.3: Migrated to use React Query hook instead of Zustand store */ export function useAuthUser(): User | null { const { data: user } = useUser(); return user ?? null; } export function useAuthStatus(): { isAuthenticated: boolean; isLoading: boolean; error: ApiError | null; } { // TEMPORARY FIX: Use direct store access instead of useShallow to isolate initialization error // TODO: Re-enable useShallow once initialization issue is resolved const store = useAuthStore(); return { isAuthenticated: store.isAuthenticated, isLoading: store.isLoading, error: store.error, }; // ORIGINAL CODE (commented for debugging): // return useAuthStore( // useShallow((state) => ({ // isAuthenticated: state.isAuthenticated, // isLoading: state.isLoading, // error: state.error, // })), // ); } export function useAuthActions() { // TEMPORARY FIX: Use direct store access instead of useShallow to isolate initialization error const store = useAuthStore(); return { login: store.login, register: store.register, logout: store.logout, refreshUser: store.refreshUser, checkAuthStatus: store.checkAuthStatus, clearError: store.clearError, setLoading: store.setLoading, }; // ORIGINAL CODE (commented for debugging): // 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' { // TEMPORARY FIX: Direct store access return useUIStore((state: UIStore) => state.theme); } export function useUILanguage(): 'en' | 'fr' { // TEMPORARY FIX: Direct store access return useUIStore((state: UIStore) => state.language); } export function useUISidebar() { const sidebarOpen = useUIStore((state: UIStore) => state.sidebarOpen); const setSidebarOpen = useUIStore((state: UIStore) => state.setSidebarOpen); return { sidebarOpen, setSidebarOpen }; } export function useUINotifications() { const notifications = useUIStore((state: UIStore) => state.notifications); const addNotification = useUIStore((state: UIStore) => state.addNotification); const removeNotification = useUIStore( (state: UIStore) => state.removeNotification, ); const markNotificationAsRead = useUIStore( (state: UIStore) => state.markNotificationAsRead, ); const clearNotifications = useUIStore( (state: UIStore) => state.clearNotifications, ); return { notifications, addNotification, removeNotification, markNotificationAsRead, clearNotifications, }; } export function useUIActions() { const setTheme = useUIStore((state: UIStore) => state.setTheme); const setLanguage = useUIStore((state: UIStore) => state.setLanguage); const setSidebarOpen = useUIStore((state: UIStore) => state.setSidebarOpen); return { setTheme, setLanguage, 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) * * @deprecated Domain data (items, favorites) have been migrated to React Query. * Use useLibraryItems() or useLibraryFavorites() instead. */ export function useLibraryItemsNormalized() { // Domain data removed from store - return empty normalized state for backward compatibility return { byId: {}, allIds: [] }; } /** * @deprecated Domain data (items, favorites) have been migrated to React Query. * Use useLibraryFavorites() instead. */ export function useLibraryFavoritesNormalized() { // Domain data removed from store - return empty normalized state for backward compatibility return { byId: {}, allIds: [] }; } export function useLibraryFilters() { const filters = useLibraryStore((state: LibraryStore) => state.filters); const setFilters = useLibraryStore((state: LibraryStore) => state.setFilters); return { filters, setFilters }; } export function useLibraryPagination(params: LibraryItemsParams = {}) { const { data } = useLibraryItemsQuery(params); return { page: data?.page ?? 1, limit: data?.limit ?? 20, total: data?.total ?? 0, // Map snake_case from API to camelCase for backward compatibility hasNext: data?.has_next ?? false, hasPrev: data?.has_prev ?? false, // Also provide snake_case for API compatibility 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(); 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((state: ChatStore) => state.conversations); } export function useChatCurrentConversation(): Conversation | null { return useChatStore((state: ChatStore) => state.currentConversation); } export function useChatMessages(conversationId: string): ChatMessage[] { return useChatStore( (state: ChatStore) => state.messages[conversationId] || [], ); } export function useChatTypingUsers(conversationId: string): string[] { return useChatStore( (state: ChatStore) => state.typingUsers[conversationId] || [], ); } export function useChatConnection(): { isConnected: boolean; isLoading: boolean; error: string | null; } { const isConnected = useChatStore((state: ChatStore) => state.isConnected); const isLoading = useChatStore((state: ChatStore) => state.isLoading); const error = useChatStore((state: ChatStore) => state.error); return { isConnected, isLoading, error }; } export function useChatActions() { const store = useChatStore(); return { setConversations: store.setConversations, setCurrentConversation: store.setCurrentConversation, addMessage: store.addMessage, updateMessage: store.updateMessage, removeMessage: store.removeMessage, setMessages: store.setMessages, setTypingUsers: store.setTypingUsers, addTypingUser: store.addTypingUser, removeTypingUser: store.removeTypingUser, setConnected: store.setConnected, setLoading: store.setLoading, setError: store.setError, connect: store.connect, disconnect: store.disconnect, joinConversation: store.joinConversation, leaveConversation: store.leaveConversation, sendMessage: store.sendMessage, startTyping: store.startTyping, stopTyping: store.stopTyping, addReaction: store.addReaction, removeReaction: store.removeReaction, fetchConversations: store.fetchConversations, createConversation: store.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 { // TEMPORARY FIX: Direct store access instead of useShallow to avoid React.Children error return store(selector); // ORIGINAL CODE (commented for debugging): // return store(useShallow(selector)); }