Server State Management
Comprehensive guide for managing server state with TanStack Query in our React Native application
Server State Management
Overview
This document outlines our approach to managing server state - data that originates from external APIs and requires caching, synchronization, and complex loading states. Our architecture implements TanStack Query (React Query) as the dedicated solution for all server state management, following our application's Golden Rule: "Server state belongs in TanStack Query, not in Zustand or Redux". This approach optimizes for performance, developer experience, and data consistency across the application.
Purpose & Scope
- Target Audience: React Native developers implementing features that require API data fetching, caching, or state synchronization
- Problems Addressed: API data management, caching strategies, loading/error states, and data synchronization challenges
- Scope Boundaries: Covers TanStack Query implementation, query patterns, mutation strategies, and caching approaches but does not cover API service implementation details or authentication token management
Core Components/Architecture
Component Types
| Component | Responsibility | Implementation |
|---|---|---|
| Query Hooks | Data fetching and caching | Feature-specific custom hooks |
| Mutation Hooks | Data updating and optimistic updates | Feature-specific custom hooks |
| Query Client | Central management and configuration | Globally configured instance |
| Query Cache | In-memory data storage | Managed by Query Client |
| Query Keys | Cache identification and organization | Structured array patterns |
When to Use Server State
TanStack Query should be used for any data that meets these criteria:
- (Do ✅) Use for data fetched from external APIs or backend services
- (Do ✅) Use for data that requires caching and deduplication
- (Do ✅) Use for data with complex loading or error states
- (Do ✅) Use for data that needs synchronization across components
- (Do ✅) Use for real-time data that requires polling or refetching
Design Principles
Core Architectural Principles
-
(Do ✅) Keep server state separate from client state
- Server state should be managed by TanStack Query
- Client state should be managed by Zustand
- Mixing these concerns leads to unnecessary complexity
-
(Do ✅) Create focused, reusable query hooks
- Each query hook should serve a specific data need
- Hooks should be feature-specific and located in the appropriate feature directory
- Abstract complex query logic into custom hooks
-
(Don't ❌) Store server data in Zustand or React context
- Avoid creating duplicate copies of server state
- Let TanStack Query handle caching and synchronization
Data Access Patterns
| Pattern | Used For | Implementation |
|---|---|---|
| Basic Queries | Simple data fetching | useQuery with query keys |
| Dependent Queries | Data that depends on other queries | useQuery with enabled option |
| Paginated Queries | Lists with pagination | useQuery with pagination params |
| Infinite Queries | Endless scrolling lists | useInfiniteQuery with getNextPageParam |
| Mutations | Creating, updating, deleting data | useMutation with callbacks |
| Optimistic Updates | Immediate UI feedback | useMutation with onMutate |
Query Key Strategies
Query keys should follow a consistent structure:
Trade-offs and Design Decisions
Centralized vs. Distributed Query Hooks
| Approach | Benefits | Drawbacks | Best For |
|---|---|---|---|
| Centralized API hooks | Consistency, single source of truth | Potential for large files, tight coupling | Small to medium apps |
| Feature-based hooks | Better code organization, feature isolation | Potential duplication | Medium to large apps |
Our Decision: We use a feature-based organization for query hooks while sharing common functionality through a factory pattern, giving us both organization and reusability.
Implementation Considerations
Performance Implications
-
(Do ✅) Configure appropriate staleTime based on data volatility
- More static data (e.g., product details): Longer staleTime (5-10 minutes)
- More dynamic data (e.g., cart): Shorter staleTime (0-30 seconds)
-
(Do ✅) Use the
selectoption to transform and minimize data- Transform and filter data on selection to minimize re-renders
- Extract only the needed fields from larger response objects
-
(Don't ❌) Fetch the same data in multiple components
- Create reusable query hooks to share data between components
- Let TanStack Query handle the caching and deduplication
Security Considerations
-
(Do ✅) Validate all data from server responses
- Use TypeScript interfaces to enforce data shape
- Consider runtime validation with libraries like Zod or Yup
-
(Do ✅) Properly handle authentication errors
- Set up global error handling for 401 responses
- Redirect to login page or refresh tokens as needed
-
(Don't ❌) Store sensitive data in the query cache without encryption
- Be cautious when persisting the query cache to storage
- Consider what data should be excluded from persistence
Scalability Aspects
-
(Do ✅) Implement structured query keys for efficient invalidation
- Create a query key factory for consistent key structure
- Use parent keys to invalidate related queries
-
(Consider 🤔) Setting up query cache persistence
- Improves offline experience and reduces initial loading
- Balance with cache size management and potential staleness
-
(Be Aware ❗) Of memory usage with large datasets
- Implement pagination or infinite queries for large lists
- Consider data normalization for frequently used entities