veza/apps/web/src/utils/storeSelectors.ts
senke bed9119122 cleanup: remove deprecated unused functions (Cleanup 17)
- Removed useLibraryItemsNormalized() - deprecated, no imports found
- Removed useLibraryFavoritesNormalized() - deprecated, no imports found
- Functions were marked @deprecated and only returned empty states
- Migration to React Query completed, these functions no longer needed
- Cleanup 17: In progress - removed deprecated dead code
2026-01-16 12:41:37 +01:00

314 lines
10 KiB
TypeScript

/**
* 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 } from '@/features/chat/store/chatStore';
import type { ChatState, ChatMessage, Conversation } from '@/features/chat/store/chatStore';
import {
useLibraryItems as useLibraryItemsQuery,
useLibraryFavorites as useLibraryFavoritesQuery,
libraryQueryKeys,
type LibraryItemsParams,
} from '@/features/library/hooks/useLibraryItems';
import type { User, LibraryItem } 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 ?? [];
}
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
* Action 4.5.1.5: Updated to use feature store (features/chat/store/chatStore.ts)
*/
export function useChatConversations(): Conversation[] {
return useChatStore((state: ChatState) => state.conversations);
}
export function useChatCurrentConversationId(): string | null {
return useChatStore((state: ChatState) => state.currentConversationId);
}
export function useChatMessages(conversationId: string): ChatMessage[] {
return useChatStore(
(state: ChatState) => state.messages[conversationId] || [],
);
}
export function useChatTypingUsers(conversationId: string): string[] {
return useChatStore(
(state: ChatState) => state.typingUsers[conversationId] || [],
);
}
export function useChatConnection(): {
wsStatus: 'disconnected' | 'connecting' | 'connected' | 'error';
} {
const wsStatus = useChatStore((state: ChatState) => state.wsStatus);
return { wsStatus };
}
export function useChatActions() {
const store = useChatStore();
return {
setUserId: store.setUserId,
setWsToken: store.setWsToken,
setWsStatus: store.setWsStatus,
addConversation: store.addConversation,
setCurrentConversation: store.setCurrentConversation,
addMessage: store.addMessage,
loadMessages: store.loadMessages,
addReaction: store.addReaction,
removeReaction: store.removeReaction,
setUserTyping: store.setUserTyping,
};
}
/**
* 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<T, U>(
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));
}