diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json
index 93b57c666..9b52ae895 100644
--- a/VEZA_COMPLETE_MVP_TODOLIST.json
+++ b/VEZA_COMPLETE_MVP_TODOLIST.json
@@ -8952,7 +8952,7 @@
"description": "Optimize state selectors to prevent unnecessary re-renders",
"owner": "frontend",
"estimated_hours": 4,
- "status": "todo",
+ "status": "completed",
"files_involved": [],
"implementation_steps": [
{
@@ -8973,7 +8973,8 @@
"Unit tests",
"Integration tests"
],
- "notes": ""
+ "notes": "Created storeSelectors.ts with optimized selectors for all Zustand stores (auth, ui, library, chat). Selectors use useShallow from zustand/react/shallow to prevent unnecessary re-renders. Created STATE_SELECTORS.md documentation explaining how to use optimized selectors and best practices. Selectors are organized by store and provide granular access to state properties and actions.",
+ "completed_at": "2025-12-25T12:58:51.832451+00:00"
},
{
"id": "FE-STATE-009",
diff --git a/apps/web/src/docs/STATE_SELECTORS.md b/apps/web/src/docs/STATE_SELECTORS.md
new file mode 100644
index 000000000..34530e209
--- /dev/null
+++ b/apps/web/src/docs/STATE_SELECTORS.md
@@ -0,0 +1,284 @@
+# State Selectors Optimization Guide
+
+## FE-STATE-008: Optimize state selectors to prevent unnecessary re-renders
+
+This document explains how to use optimized selectors for Zustand stores to prevent unnecessary re-renders in React components.
+
+## Problem
+
+When you access a Zustand store directly, the component re-renders whenever **any** part of the store changes, even if you're only using a small portion of it:
+
+```typescript
+// ❌ BAD: Re-renders on ANY store change
+function MyComponent() {
+ const { user, isAuthenticated } = useAuthStore();
+ // This component re-renders even if only `isLoading` changes
+ return
{user?.username}
;
+}
+```
+
+## Solution
+
+Use optimized selectors that only re-render when the selected values actually change:
+
+```typescript
+// ✅ GOOD: Only re-renders when `user` changes
+function MyComponent() {
+ const user = useAuthUser();
+ return {user?.username}
;
+}
+```
+
+## Available Selectors
+
+### Auth Store Selectors
+
+```typescript
+import {
+ useAuthUser,
+ useAuthStatus,
+ useAuthActions,
+} from '@/utils/storeSelectors';
+
+// Get only the user
+const user = useAuthUser();
+
+// Get authentication status
+const { isAuthenticated, isLoading, error } = useAuthStatus();
+
+// Get only actions (never causes re-renders)
+const { login, logout } = useAuthActions();
+```
+
+### UI Store Selectors
+
+```typescript
+import {
+ useUITheme,
+ useUILanguage,
+ useUISidebar,
+ useUINotifications,
+ useUIActions,
+} from '@/utils/storeSelectors';
+
+// Get only theme
+const theme = useUITheme();
+
+// Get sidebar state and action
+const { sidebarOpen, setSidebarOpen } = useUISidebar();
+
+// Get notifications
+const { notifications, addNotification } = useUINotifications();
+```
+
+### Library Store Selectors
+
+```typescript
+import {
+ useLibraryItems,
+ useLibraryFavorites,
+ useLibraryFilters,
+ useLibraryPagination,
+ useLibraryStatus,
+ useLibraryActions,
+} from '@/utils/storeSelectors';
+
+// Get only items
+const items = useLibraryItems();
+
+// Get favorites
+const favorites = useLibraryFavorites();
+
+// Get filters and setter
+const { filters, setFilters } = useLibraryFilters();
+
+// Get pagination info
+const pagination = useLibraryPagination();
+```
+
+### Chat Store Selectors
+
+```typescript
+import {
+ useChatConversations,
+ useChatCurrentConversation,
+ useChatMessages,
+ useChatTypingUsers,
+ useChatConnection,
+ useChatActions,
+} from '@/utils/storeSelectors';
+
+// Get conversations
+const conversations = useChatConversations();
+
+// Get messages for a specific conversation
+const messages = useChatMessages(conversationId);
+
+// Get typing users for a conversation
+const typingUsers = useChatTypingUsers(conversationId);
+
+// Get connection status
+const { isConnected, isLoading, error } = useChatConnection();
+```
+
+## Custom Selectors
+
+If you need to select multiple values that aren't covered by the provided selectors, use `useStoreSelector`:
+
+```typescript
+import { useStoreSelector } from '@/utils/storeSelectors';
+import { useAuthStore } from '@/stores/auth';
+
+function MyComponent() {
+ // Select multiple values with shallow comparison
+ const { user, isAuthenticated, isLoading } = useStoreSelector(
+ useAuthStore,
+ (state) => ({
+ user: state.user,
+ isAuthenticated: state.isAuthenticated,
+ isLoading: state.isLoading,
+ }),
+ );
+
+ return {user?.username}
;
+}
+```
+
+## Best Practices
+
+### 1. Use Specific Selectors
+
+Prefer specific selectors over accessing the entire store:
+
+```typescript
+// ❌ BAD
+const store = useAuthStore();
+
+// ✅ GOOD
+const user = useAuthUser();
+const { isAuthenticated } = useAuthStatus();
+```
+
+### 2. Separate Data from Actions
+
+Actions rarely change, so separate them to avoid re-renders:
+
+```typescript
+// ❌ BAD: Re-renders when any state changes
+const { items, fetchItems } = useLibraryStore();
+
+// ✅ GOOD: Only re-renders when items change
+const items = useLibraryItems();
+const { fetchItems } = useLibraryActions();
+```
+
+### 3. Use Shallow Comparison for Objects
+
+When selecting objects, use `useShallow` or the provided selectors:
+
+```typescript
+// ❌ BAD: New object reference on every render
+const { user, isAuthenticated } = useAuthStore((state) => ({
+ user: state.user,
+ isAuthenticated: state.isAuthenticated,
+}));
+
+// ✅ GOOD: Shallow comparison prevents unnecessary re-renders
+const { user, isAuthenticated } = useStoreSelector(
+ useAuthStore,
+ (state) => ({
+ user: state.user,
+ isAuthenticated: state.isAuthenticated,
+ }),
+);
+```
+
+### 4. Memoize Derived Values
+
+If you need to compute values from store data, use `useMemo`:
+
+```typescript
+import { useMemo } from 'react';
+import { useLibraryItems } from '@/utils/storeSelectors';
+
+function MyComponent() {
+ const items = useLibraryItems();
+
+ // Memoize expensive computation
+ const favoriteCount = useMemo(
+ () => items.filter((item) => item.is_favorite).length,
+ [items],
+ );
+
+ return Favorites: {favoriteCount}
;
+}
+```
+
+## Performance Benefits
+
+Using optimized selectors provides several benefits:
+
+1. **Fewer Re-renders**: Components only re-render when their selected values change
+2. **Better Performance**: Reduces unnecessary React reconciliation
+3. **Predictable Updates**: Easier to reason about when components update
+4. **Better DevTools**: Clearer view of what triggers re-renders
+
+## Migration Guide
+
+### Before (Unoptimized)
+
+```typescript
+function UserProfile() {
+ const { user, isAuthenticated, isLoading, error } = useAuthStore();
+ const { theme, language } = useUIStore();
+
+ // Re-renders on ANY change to auth or UI stores
+ return {user?.username}
;
+}
+```
+
+### After (Optimized)
+
+```typescript
+import {
+ useAuthUser,
+ useUITheme,
+ useUILanguage,
+} from '@/utils/storeSelectors';
+
+function UserProfile() {
+ const user = useAuthUser();
+ const theme = useUITheme();
+ const language = useUILanguage();
+
+ // Only re-renders when user, theme, or language actually change
+ return {user?.username}
;
+}
+```
+
+## Testing
+
+When testing components that use selectors, you can still use the store directly:
+
+```typescript
+import { render } from '@testing-library/react';
+import { useAuthStore } from '@/stores/auth';
+
+test('renders user profile', () => {
+ // Set up store state
+ useAuthStore.setState({
+ user: { id: '1', username: 'testuser' },
+ isAuthenticated: true,
+ });
+
+ const { getByText } = render();
+ expect(getByText('testuser')).toBeInTheDocument();
+});
+```
+
+## Related Documentation
+
+- [Zustand Selectors](https://github.com/pmndrs/zustand#selecting-multiple-state-slices)
+- [Zustand Shallow Comparison](https://github.com/pmndrs/zustand#shallow-equality-check)
+- [React Performance Optimization](https://react.dev/learn/render-and-commit)
+
diff --git a/apps/web/src/utils/storeSelectors.ts b/apps/web/src/utils/storeSelectors.ts
new file mode 100644
index 000000000..718f2511f
--- /dev/null
+++ b/apps/web/src/utils/storeSelectors.ts
@@ -0,0 +1,198 @@
+/**
+ * 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 { useAuthStore } from '@/stores/auth';
+import { useUIStore } from '@/stores/ui';
+import { useLibraryStore } from '@/stores/library';
+import { useChatStore } from '@/stores/chat';
+
+/**
+ * FE-STATE-008: Optimized selectors for AuthStore
+ *
+ * These hooks only re-render when the selected values actually change.
+ */
+export function useAuthUser() {
+ return useAuthStore(useShallow((state) => state.user));
+}
+
+export function useAuthStatus() {
+ 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() {
+ return useUIStore(useShallow((state) => state.theme));
+}
+
+export function useUILanguage() {
+ return useUIStore(useShallow((state) => state.language));
+}
+
+export function useUISidebar() {
+ return useUIStore(useShallow((state) => ({
+ sidebarOpen: state.sidebarOpen,
+ setSidebarOpen: state.setSidebarOpen,
+ })));
+}
+
+export function useUINotifications() {
+ return useUIStore(useShallow((state) => ({
+ notifications: state.notifications,
+ addNotification: state.addNotification,
+ removeNotification: state.removeNotification,
+ markNotificationAsRead: state.markNotificationAsRead,
+ clearNotifications: state.clearNotifications,
+ })));
+}
+
+export function useUIActions() {
+ return useUIStore(useShallow((state) => ({
+ setTheme: state.setTheme,
+ setLanguage: state.setLanguage,
+ setSidebarOpen: state.setSidebarOpen,
+ })));
+}
+
+/**
+ * FE-STATE-008: Optimized selectors for LibraryStore
+ */
+export function useLibraryItems() {
+ return useLibraryStore(useShallow((state) => state.items));
+}
+
+export function useLibraryFavorites() {
+ return useLibraryStore(useShallow((state) => state.favorites));
+}
+
+export function useLibraryFilters() {
+ return useLibraryStore(useShallow((state) => ({
+ filters: state.filters,
+ setFilters: state.setFilters,
+ })));
+}
+
+export function useLibraryPagination() {
+ return useLibraryStore(useShallow((state) => state.pagination));
+}
+
+export function useLibraryStatus() {
+ return useLibraryStore(useShallow((state) => ({
+ isLoading: state.isLoading,
+ error: state.error,
+ })));
+}
+
+export function useLibraryActions() {
+ return useLibraryStore(useShallow((state) => ({
+ fetchItems: state.fetchItems,
+ fetchFavorites: state.fetchFavorites,
+ uploadFile: state.uploadFile,
+ toggleFavorite: state.toggleFavorite,
+ deleteItem: state.deleteItem,
+ clearItems: state.clearItems,
+ })));
+}
+
+/**
+ * FE-STATE-008: Optimized selectors for ChatStore
+ */
+export function useChatConversations() {
+ return useChatStore(useShallow((state) => state.conversations));
+}
+
+export function useChatCurrentConversation() {
+ return useChatStore(useShallow((state) => state.currentConversation));
+}
+
+export function useChatMessages(conversationId: string) {
+ return useChatStore(useShallow((state) => state.messages[conversationId] || []));
+}
+
+export function useChatTypingUsers(conversationId: string) {
+ return useChatStore(useShallow((state) => state.typingUsers[conversationId] || []));
+}
+
+export function useChatConnection() {
+ return useChatStore(useShallow((state) => ({
+ isConnected: state.isConnected,
+ isLoading: state.isLoading,
+ error: state.error,
+ })));
+}
+
+export function useChatActions() {
+ return useChatStore(useShallow((state) => ({
+ 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));
+}
+