Repository navigation
Data Fetching & State
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
This document explains PersonalLearningPro’s data fetching and state management patterns with a focus on TanStack Query integration. It covers queryClient configuration, caching strategies, optimistic updates, server state synchronization, automatic refetching, cache invalidation, error handling, loading states, and data transformation. It also addresses performance optimization for large datasets, pagination, and real-time updates via WebSockets. Practical examples demonstrate how to use queryClient across chat message synchronization and user data management.
The data layer centers around:
- A global queryClient configured with strict caching and error behavior
- REST API helpers for chat resources
- React components using TanStack Query to fetch and manage conversations and messages
- A WebSocket hook for real-time events
- UI components that orchestrate optimistic updates and cache synchronization
graph TB
subgraph "Client Data Layer"
QC["queryClient.ts<br/>QueryClient + default options"]
API["chat-api.ts<br/>REST helpers"]
WS["use-chat-ws.ts<br/>WebSocket hook"]
TYPES["chat.ts<br/>Types"]
end
subgraph "UI Components"
LAYOUT["ChatLayout.tsx<br/>Conversations + Channels"]
THREAD["ChatThread.tsx<br/>Messages + Pagination"]
LIST["ConversationList.tsx<br/>UI list"]
MOCK["mockData.ts<br/>Fallback data"]
PAGES["messages.tsx<br/>Page wrapper"]
end
QC --> LAYOUT
QC --> THREAD
API --> LAYOUT
API --> THREAD
WS --> THREAD
TYPES --> THREAD
TYPES --> LAYOUT
MOCK --> LAYOUT
PAGES --> LAYOUT
Diagram sources
- queryClient.ts
- chat-api.ts
- use-chat-ws.ts
- ChatLayout.tsx
- ChatThread.tsx
- ConversationList.tsx
- chat.ts
- mockData.ts
- messages.tsx
Section sources
- queryClient.ts
- chat-api.ts
- use-chat-ws.ts
- ChatLayout.tsx
- ChatThread.tsx
- ConversationList.tsx
- chat.ts
- mockData.ts
- messages.tsx
- Global queryClient with default queryFn and strict caching:
- Default queryFn validates response status and throws on errors
- Stale time is Infinity for manual cache control
- Refetching on window focus and interval disabled globally
- Retry disabled for predictable UX
- REST API helpers:
- Centralized fetch with credentials and JSON handling
- Typed DTOs for workspaces, channels, DMs, and messages
- WebSocket hook:
- Connects to chat WS, manages reconnection with exponential backoff
- Emits typed events for new messages, typing, read receipts, presence
- UI orchestration:
- ChatLayout fetches workspaces, channels, and DMs
- ChatThread fetches paginated messages, applies optimistic updates, and invalidates cache on WS events
- ConversationList renders UI list with search and grouping
Section sources
- queryClient.ts
- chat-api.ts
- use-chat-ws.ts
- ChatLayout.tsx
- ChatThread.tsx
- ConversationList.tsx
The system combines TanStack Query for server state with a WebSocket for real-time updates. The flow:
- UI triggers queries via useQuery
- queryClient executes default queryFn or provided queryFn
- REST endpoints return normalized data transformed into UI types
- WebSocket events trigger cache updates or invalidations
- UI reflects loading states, optimistic updates, and synchronized server state
sequenceDiagram
participant UI as "ChatThread.tsx"
participant QC as "queryClient.ts"
participant API as "chat-api.ts"
participant WS as "use-chat-ws.ts"
participant SVR as "Server"
UI->>QC : useQuery({ queryKey, queryFn })
QC->>API : fetchMessages(...)
API->>SVR : GET /api/channels/ : id/messages
SVR-->>API : 200 JSON messages
API-->>QC : parsed messages
QC-->>UI : data, isLoading=false
SVR-->>WS : broadcast new_message
WS-->>UI : onNewMessage event
UI->>QC : invalidateQueries(['messages', channelId])
UI->>QC : setQueryData([...older, ...existing])
UI->>SVR : sendMessage(...) via WS
SVR-->>WS : deliver new_message
WS-->>UI : onNewMessage
UI->>QC : invalidateQueries(['messages', channelId])
Diagram sources
- ChatThread.tsx
- queryClient.ts
- chat-api.ts
- use-chat-ws.ts
- Default queryFn:
- Fetches by URL from queryKey[0]
- Throws on non-OK responses
- Supports 401 handling via on401 option
- Global defaults:
- staleTime: Infinity for deterministic cache lifecycle
- refetchOnWindowFocus: false to avoid unwanted network churn
- refetchInterval: false for manual refresh control
- retry: false for explicit error handling
- Usage patterns:
- Queries pass queryKey and queryFn
- Mutations use useMutation (see OCR test components)
- Cache updates via useQueryClient: setQueryData, invalidateQueries
flowchart TD
Start(["Query Execution"]) --> Fetch["Fetch by queryKey[0]"]
Fetch --> Ok{"res.ok?"}
Ok --> |No| ThrowErr["Throw Error(status, text)"]
Ok --> |Yes| Parse["Parse JSON"]
Parse --> Return["Return data"]
ThrowErr --> End(["Stop"])
Return --> End
Diagram sources
- queryClient.ts
Section sources
- queryClient.ts
- Provides typed DTOs for workspaces, channels, DMs, and messages
- Centralized fetch helper with credentials and JSON handling
- Functions:
- fetchWorkspaces, fetchChannels, fetchDMs
- fetchMessages with limit and before pagination
- uploadFile for attachments
classDiagram
class ApiWorkspace {
+number id
+string name
+number ownerId
+number[] members
}
class ApiChannel {
+number id
+string name
+enum type
+number? workspaceId
+string? subject
+string? class
+number[]? pinnedMessages
}
class ApiMessage {
+number id
+number channelId
+number authorId
+string? authorUsername
+string content
+enum type
+string? fileUrl
+number[] readBy
+boolean? isHomework
+string? homeworkStatus
+string createdAt
}
class ApiDM {
+number id
+string name
+enum type
+number? workspaceId
+string? subject
+string? class
+number[]? pinnedMessages
+Partner partner?
}
class Partner {
+number id
+string username
+string? avatar
+string role
}
Diagram sources
- chat-api.ts
Section sources
- chat-api.ts
- Establishes WS connection with protocol derived from current location
- Manages reconnection with exponential backoff, stops on 4001
- Emits typed events: new_message, user_typing, message_read, user_presence
- Provides send helpers: sendMessage, sendTyping, markRead
- Maintains stable refs for callbacks and channel transitions
sequenceDiagram
participant Hook as "useChat-ws.ts"
participant WS as "WebSocket"
participant UI as "ChatThread.tsx"
Hook->>WS : connect()
WS-->>Hook : onopen
Hook->>WS : send(join_channel)
WS-->>Hook : onmessage(new_message)
Hook-->>UI : onNewMessage
UI->>Hook : sendMessage(...)
WS-->>Hook : onmessage(message_read)
Hook-->>UI : onRead
Diagram sources
- use-chat-ws.ts
- ChatThread.tsx
Section sources
- use-chat-ws.ts
- Fetches workspaces, channels, and DMs with TanStack Query
- Transforms server channels to UI Conversation shape
- Merges DM partner info and categorization
- Falls back to mock data when server returns none
- Maintains active conversation and toggles sidebar/thread views
flowchart TD
A["fetchWorkspaces"] --> B["fetchChannels per workspace"]
C["fetchDMs"] --> D["Transform to Conversation"]
B --> E["Merge into conversations"]
D --> E
E --> F{"Server data available?"}
F --> |Yes| G["Use server conversations"]
F --> |No| H["Use mock conversations"]
Diagram sources
- ChatLayout.tsx
- mockData.ts
Section sources
- ChatLayout.tsx
- mockData.ts
- Uses useQuery to fetch initial batch of messages with select transformation
- Maintains optimistic messages appended via WebSocket
- On new_message:
- Replace optimistic duplicates
- Invalidate messages queryKey to refetch fresh data
- On message_read:
- Update readBy in cache via setQueryData
- Infinite scroll:
- Observe top sentinel to prepend older messages
- setQueryData([...older, ...existing])
- Read receipts:
- IntersectionObserver marks messages as read via WS
- Loading state:
- Shows loading indicator while initial fetch resolves
flowchart TD
Start(["Render ChatThread"]) --> Q["useQuery(messages)"]
Q --> Loaded{"data + !isLoading?"}
Loaded --> |No| Loading["Show loading"]
Loaded --> |Yes| Sync["Sync server + optimistic"]
WS["onNewMessage"] --> OptReplace["Replace optimistic duplicates"]
OptReplace --> Invalidate["invalidateQueries(['messages', chId])"]
WS["onRead"] --> UpdateRead["setQueryData(readBy)"]
Scroll["Top sentinel intersect"] --> LoadOlder["fetchMessages(..., before)"]
LoadOlder --> Prepend["setQueryData([older, ...existing])"]
Sync --> Render["Render combined list"]
Render --> End(["Idle"])
Diagram sources
- ChatThread.tsx
- use-chat-ws.ts
Section sources
- ChatThread.tsx
- use-chat-ws.ts
- Filters and groups conversations by category
- Displays unread counts and last message previews
- Handles selection to open thread view
Section sources
- ConversationList.tsx
- ServerMessage vs UI Message differences:
- ServerMessage uses numeric IDs and different field names
- UI Message normalizes to string IDs and consistent fields
- Transformation helper ensures compatibility between server and UI shapes
Section sources
- chat.ts
- ChatThread.tsx
- Firebase integration provides user profiles and auth state
- Roles drive conversation categorization and UI behavior
- Auth helpers centralize login, registration, and profile updates
Section sources
- firebase.ts
- queryClient.ts defines the global cache policy and default queryFn
- chat-api.ts encapsulates REST endpoints and DTOs
- use-chat-ws.ts provides real-time event handling
- ChatLayout.tsx orchestrates server state fetching and merges with mocks
- ChatThread.tsx coordinates optimistic updates and cache synchronization
- ConversationList.tsx depends on UI types and role context
- messages.tsx wires the ChatLayout into the routing layer
graph LR
QC["queryClient.ts"] --> L["ChatLayout.tsx"]
QC --> T["ChatThread.tsx"]
API["chat-api.ts"] --> L
API --> T
WS["use-chat-ws.ts"] --> T
TYPES["chat.ts"] --> T
TYPES --> L
MOCK["mockData.ts"] --> L
P["messages.tsx"] --> L
Diagram sources
- queryClient.ts
- chat-api.ts
- use-chat-ws.ts
- ChatLayout.tsx
- ChatThread.tsx
- chat.ts
- mockData.ts
- messages.tsx
Section sources
- queryClient.ts
- chat-api.ts
- use-chat-ws.ts
- ChatLayout.tsx
- ChatThread.tsx
- chat.ts
- mockData.ts
- messages.tsx
- Caching
- staleTime: Infinity enforces explicit cache invalidation
- Manual invalidation on WS events prevents stale UI
- Pagination
- fetchMessages supports before-based pagination
- setQueryData prepends older messages to avoid re-fetching entire history
- Real-time updates
- WebSocket minimizes redundant polling
- Optimistic UI reduces perceived latency; invalidated on confirm
- Loading states
- isLoading guards prevent rendering empty lists during initial fetch
- Large datasets
- Prefer chunked fetches and selective cache updates
- Avoid global refetchOnWindowFocus to reduce network usage
[No sources needed since this section provides general guidance]
- Non-OK responses
- queryClient.defaultOptions.queries.queryFn throws on non-OK responses
- chat-api helpers throw with status and body text
- 401 handling
- getQueryFn supports on401: "returnNull" or "throw"
- Consider custom unauthorized behavior per query if needed
- WebSocket errors
- 4001 closes connection without reconnecting
- Exponential backoff applied otherwise; logs warnings on server errors
- Cache anomalies
- Use invalidateQueries to force fresh data after WS events
- Use setQueryData for targeted updates (e.g., readBy)
Section sources
- queryClient.ts
- chat-api.ts
- use-chat-ws.ts
PersonalLearningPro integrates TanStack Query for robust server state management, complemented by a WebSocket layer for real-time updates. The system emphasizes explicit cache control, optimistic UI, and precise cache invalidation to keep the UI consistent with server state. REST helpers and typed DTOs ensure safe transformations, while UI components orchestrate loading states, pagination, and user interactions. This approach scales to large datasets and provides responsive, reliable experiences for chat and user data workflows.