# 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 '@/features/auth/store/authStore'; 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 '@/features/auth/store/authStore'; 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)