state management strategy
React state on App and AuthenticatedApp, Zustand for theme, localStorage for credentials and the attendance goal. TanStack Query is wired for Cloudflare stats.
See Data Layer & API Integration. Theme store is Theme System.
Overview#
useStatefor auth and feature caches- Props from
AuthenticatedAppinto feature pages - Zustand persist for theme (
src/stores/theme-store.ts) localStorageforusername,password,attendanceGoal- TanStack Query in
src/hooks/cloudflare.ts(useFetchWebAnalyticsAggregateand siblings)
There is no Redux. Academic data is colocated on AuthenticatedApp so hash route changes keep caches.
State hierarchy#
flowchart TD App["App isAuthenticated isDemoMode isLoading error"] --> AA["AuthenticatedApp feature slices"] AA --> Pages["Attendance Grades Exams Subjects Profile"] Store["useThemeStore"] --> ThemeUI["ThemeProvider Header"] QC["QueryClient"] --> CF["Cloudflare.jsx"]
App-level state#
| Variable | Type | Persistence |
|---|---|---|
isAuthenticated |
boolean | no |
isDemoMode |
boolean | no |
isLoading |
boolean | no |
error |
string or null | no |
flowchart TD
Mount["App mount"] --> Creds{"username and password?"}
Creds -->|yes| Login["realPortal.student_login"]
Login -->|session| Auth["isAuthenticated true isDemoMode false"]
Login -->|fail| Clear["clear storage isAuthenticated false"]
Creds -->|no| LoginUI["LoginWrapper"]Portals at module scope: realPortal, mockPortal. activePortal = isDemoMode ? mockPortal : realPortal.
AuthenticatedApp state coordination#
Attendance state cluster#
flowchart TD Att["Attendance slice"] --> Data["attendanceData"] Att --> Sem["attendanceSemestersData selectedAttendanceSem"] Att --> Goal["attendanceGoal localStorage"] Att --> Daily["subjectAttendanceData selectedSubject"] Att --> UI["activeAttendanceTab dailyDate calendar tracker cacheStatus"] Att --> Load["isAttendanceMetaLoading isAttendanceDataLoading"]
Goal init:
const [attendanceGoal, setAttendanceGoal] = useState(() => {
const savedGoal = localStorage.getItem("attendanceGoal");
return savedGoal ? parseInt(savedGoal) : 75;
});
Grades state cluster#
flowchart TD Gr["Grades slice"] --> Overview["gradesData gradesSemesterData"] Gr --> Cards["gradeCardSemesters selectedGradeCardSem gradeCard gradeCards"] Gr --> Marks["marksSemesters selectedMarksSem marksSemesterData marksData"] Gr --> UI["activeGradesTab gradesLoading gradesError gradeCardLoading marksLoading download dialog"]
Other feature state clusters#
Subjects: subjectData, subjectSemestersData, subjectChoices, selectedSubjectsSem, activeSubjectsTab, loading flags.
Exams: examSchedule, examSemesters, selectedExamSem, selectedExamEvent. examEvents and loading stay local in Exams.jsx.
Profile: profileData.
Props drilling pattern#
Attendance component props#
flowchart LR AA["AuthenticatedApp"] --> Att["Attendance"] Att --> Card["AttendanceCard"] AA -->|"20+ props"| Att
Grades component props#
Grades receives caches (gradesData, gradeCards, marksData), tab state, semester lists, and setters. Same hub pattern.
Data caching strategy#
Cache-by-ID pattern#
flowchart TD
Change["user picks semester"] --> Hit{"cache[registration_id]?"}
Hit -->|yes| Use["render cached"]
Hit -->|no| Fetch["w.get_attendance or get_grade_card"]
Fetch --> Save["setState prev spread key"]Attendance handleSemesterChange checks attendanceData[value] before w.get_attendance(header, semester).
Persistence mechanisms#
| Key | Purpose |
|---|---|
username |
Auto-login |
password |
Auto-login |
attendanceGoal |
Target percent |
| Zustand persist | Theme preset and styles |
Auto-login flow#
sequenceDiagram participant App participant LS as localStorage participant Portal as realPortal App->>LS: getItem username password App->>Portal: student_login Portal-->>App: session or throw
State initialization patterns#
Example: attendance initialization#
flowchart TD
Mount["Attendance mount"] --> Meta{"semestersData?"}
Meta -->|no| GetMeta["w.get_attendance_meta"]
GetMeta --> Latest["meta.latest_semester"]
Latest --> GetAtt["w.get_attendance header latestSem"]
GetAtt -->|NO Attendance Found| Prev["previous semester"]Caches on AuthenticatedApp mean returning to /attendance does not refetch.
State update patterns#
Immutable spreads:
setAttendanceData(prev => ({
...prev,
[semesterId]: newData
}));
Loading flags wrap async work (setMarksLoading(true) / finally false).
TanStack query integration#
App creates new QueryClient() and wraps the tree in QueryClientProvider.
Attendance and Grades do not call useQuery. /stats does:
useFetchWebAnalyticsAggregateuseFetchWebAnalyticsBrowseruseFetchWebAnalyticsOSuseFetchWebAnalyticsSparkline
Those hooks live in src/hooks/cloudflare.ts and POST to /api/analytics.
Subject-specific caching: daily attendance#
flowchart TD Tab["activeTab daily"] --> All["Promise.all subjects"] All --> Status["subjectCacheStatus idle fetching cached"] Status --> Sheet["progress Sheet CircleProgress"]
Refactoring opportunities#
Props lists are long. Cache-by-id is copied across modules. UI flags sit next to data.
flowchart LR Now["AuthenticatedApp useState"] -.-> Later["optional useQuery per feature"] Now --> Works["works at current size"]
Query is already in the tree for analytics. Academic modules have not migrated.
Summary#
App: auth and portal pickAuthenticatedApp: feature caches- Features: fetch through
w, write through setters - localStorage: credentials and goal
- Zustand: theme
- TanStack Query: Cloudflare dashboard