State Management
This page documents the state management architecture used throughout the JIIT Timetable Creator application. The system employs a three-tier approach: React Context API for runtime global state, localStorage for cross-session persistence, and nuqs for URL state synchronization. This design enables offline functionality, shareable links, and seamless state recovery across browser sessions.
For information about data type definitions, see Data Model & Types. For details on how Python processing generates schedule data, see Python Processing Pipeline.
State Management Architecture Overview
The application maintains state across three complementary layers, each serving a distinct purpose in the data lifecycle.

Sources: src/App.tsx1-841 src/context/userContextProvider.tsx1-52 src/context/userContext.tsx
UserContext: React Context Provider
The UserContext provides global state management using React's Context API. It maintains two primary state objects that are consumed by display components throughout the application.
Context Structure
The context is defined in src/context/userContext.tsx and implemented by src/context/userContextProvider.tsx1-52:

Data Types:
| State Variable | Type | Purpose |
|---|---|---|
schedule |
`YourTietable | null` |
editedSchedule |
`YourTietable | null` |
setSchedule |
`(schedule: YourTietable | null) => void` |
setEditedSchedule |
`(schedule: YourTietable | null) => void` |
Sources: src/context/userContextProvider.tsx5-16 src/context/userContext.tsx
Schedule vs EditedSchedule
The system maintains two separate schedule objects to support non-destructive editing:
schedule: The original timetable generated by Python processing. This represents the "source of truth" from the timetable JSON data.editedSchedule: User modifications overlaid on the base schedule. When non-null, this takes precedence in display components.

Sources: src/App.tsx166-167 src/App.tsx211-212 src/components/schedule-display.tsx17-40 src/components/edit-event-dialog.tsx66-127
Context Initialization and Persistence
The provider automatically loads and persists editedSchedule to localStorage:
Initialization src/context/userContextProvider.tsx18-26:
useEffect(() => {
const cachedEdited = localStorage.getItem("editedSchedule");
if (cachedEdited) {
try {
setEditedSchedule(JSON.parse(cachedEdited));
} catch {}
}
}, []);
Persistence src/context/userContextProvider.tsx29-35:
React.useEffect(() => {
if (editedSchedule) {
localStorage.setItem("editedSchedule", JSON.stringify(editedSchedule));
} else {
localStorage.removeItem("editedSchedule");
}
}, [editedSchedule]);
Sources: src/context/userContextProvider.tsx18-35
localStorage Persistence Layer
The application uses browser localStorage to persist state across sessions. Multiple keys store different aspects of the application state.
Storage Keys and Data Structures

Storage Schema:
| Key | Data Structure | Purpose |
|---|---|---|
cachedSchedule |
YourTietable (JSON) |
Base generated schedule for quick recovery |
cachedScheduleParams |
{year, batch, campus, selectedSubjects} |
Parameters used to generate current schedule |
classConfigs |
{[configName]: {year, batch, campus, selectedSubjects, electiveCount}} |
Named presets for quick loading |
editedSchedule |
YourTietable (JSON) |
User modifications to schedule |
Sources: src/App.tsx64-82 src/App.tsx85-95 src/App.tsx105-109 src/App.tsx214-222 src/context/userContextProvider.tsx18-35
cachedSchedule and cachedScheduleParams
These two keys work together to enable intelligent schedule recovery and conflict detection:
Write Logic src/App.tsx105-109 and src/App.tsx214-222:
// After successful generation
React.useEffect(() => {
if (schedule && Object.keys(schedule).length > 0) {
localStorage.setItem("cachedSchedule", JSON.stringify(schedule));
}
}, [schedule]);
// Inside handleFormSubmit
localStorage.setItem(
"cachedScheduleParams",
JSON.stringify({
year,
batch,
campus,
selectedSubjects: electives,
})
);
Read and Comparison src/App.tsx274-328:
The application compares URL parameters against cached parameters to detect conflicts:
const urlParams = new URLSearchParams(window.location.search);
const cachedParams = localStorage.getItem("cachedScheduleParams");
// Compare URL params to cached params
let isSame = false;
try {
const cachedObj = cachedParams ? JSON.parse(cachedParams) : {};
isSame =
cachedObj.year === year &&
cachedObj.batch === batch &&
cachedObj.campus === campus &&
Array.isArray(cachedObj.selectedSubjects) &&
Array.isArray(selectedSubjects) &&
cachedObj.selectedSubjects.length === selectedSubjects.length &&
cachedObj.selectedSubjects.every((s: string) =>
selectedSubjects.includes(s)
);
} catch {}
if (!isSame) {
setShowUrlParamsDialog(true); // Show conflict resolution dialog
}
Sources: src/App.tsx105-109 src/App.tsx214-222 src/App.tsx274-328
classConfigs: Saved Configurations
Users can save multiple configuration presets with custom names. These are stored as a single JSON object in localStorage:
Data Structure:
{
[configName: string]: {
year: string;
batch: string;
campus: string;
selectedSubjects: string[];
electiveCount?: number;
}
}
Initialization src/App.tsx64-69:
const [savedConfigs, setSavedConfigs] = React.useState<{
[key: string]: any;
}>(() => {
const configs = localStorage.getItem("classConfigs");
return configs ? JSON.parse(configs) : {};
});
Persistence src/App.tsx80-82:
React.useEffect(() => {
localStorage.setItem("classConfigs", JSON.stringify(savedConfigs));
}, [savedConfigs]);
Usage Flow:

Sources: src/App.tsx64-82 src/App.tsx361-386 src/App.tsx388-390
URL State Synchronization with nuqs
The application uses the nuqs library to synchronize form state with URL query parameters. This enables shareable links and browser history integration.
Managed Query Parameters

Parameter Definitions src/App.tsx231-247:
| Parameter | Parser | Default | Purpose |
|---|---|---|---|
year |
parseAsString |
"" |
Academic year (1-4) |
batch |
parseAsString |
"" |
Batch number or range |
campus |
parseAsString |
"" |
Campus code (62, 128, BCA) |
selectedSubjects |
parseAsArrayOf(parseAsString) |
[] |
Array of subject codes |
electiveCount |
parseAsInteger |
0 |
Number of electives to select |
isGenerating |
parseAsBoolean |
false |
Loading indicator state |
Sources: src/App.tsx231-247 src/App.tsx15-20
URL-Based Auto-Generation
When URL parameters are present and valid, the application automatically generates a schedule:
Detection and Auto-Generate src/App.tsx253-271:
React.useEffect(() => {
if (
!autoGeneratedRef.current &&
_year &&
_batch &&
_campus &&
_selectedSubjects &&
_selectedSubjects.length > 0
) {
autoGeneratedRef.current = true;
handleFormSubmit({
year: _year,
batch: _batch,
electives: _selectedSubjects,
campus: _campus,
});
}
}, [_year, _batch, _campus, _selectedSubjects]);
Conflict Resolution:
If URL parameters differ from cached parameters, the UrlParamsDialog presents three options:
- Override: Generate new schedule from URL parameters
- Prefill: Load URL parameters into form without generating
- View Existing: Ignore URL and display cached schedule
Sources: src/App.tsx274-328 src/App.tsx330-359 src/components/url-params-dialog.tsx1-161
State Flow and Lifecycle
The following diagram illustrates the complete state lifecycle from user interaction to persistence:

Sources: src/App.tsx154-229 src/components/schedule-display.tsx1-151 src/components/edit-event-dialog.tsx66-127
State Priority Hierarchy
When multiple state sources exist, the application follows this priority order:

Implementation src/components/schedule-display.tsx40:
const displaySchedule = editedSchedule || schedule;
Sources: src/components/schedule-display.tsx17-40 src/App.tsx85-95
State Modification Operations
Generating a New Schedule
Entry Point: src/App.tsx154-229
State Changes:
- Clear
editedSchedule→nullsrc/App.tsx167 - Set
isGenerating→truesrc/App.tsx181 - Call Python processing via Pyodide
- Set
schedule→ generated result src/App.tsx211-212 - Write
cachedScheduleandcachedScheduleParamsto localStorage - Set
isGenerating→falsesrc/App.tsx227
Editing an Event
Entry Point: src/components/edit-event-dialog.tsx66-127
State Changes:
- If
editedScheduleisnull, deep copyschedule→editedSchedule - Modify specific day/time slot in
editedSchedule - Trigger
setEditedSchedule()which persists to localStorage - Display components re-render with priority on
editedSchedule
Loading a Saved Configuration
Entry Point: src/App.tsx361-386
State Changes:
- Clear
editedSchedule→nullsrc/App.tsx366 - Load config from
classConfigsin localStorage - Update URL parameters via nuqs setters src/App.tsx369-374
- Call
handleFormSubmit()to generate schedule - Follow normal generation flow
Deleting an Event
Entry Point: src/components/edit-event-dialog.tsx130-146
State Changes:
- Create copy of
editedSchedule - Delete specific time slot from day
- If day becomes empty, delete day key
- Call
setEditedSchedule()with updated schedule - Persist to localStorage automatically
Sources: src/App.tsx154-229 src/App.tsx361-386 src/components/edit-event-dialog.tsx66-146
Data Structure Details
YourTietable Type
The core data structure for both schedule and editedSchedule:
interface YourTietable {
[day: string]: {
[timeSlot: string]: {
subject_name: string;
type: "L" | "T" | "P" | "C";
location: string;
isCustom?: boolean; // Added by EditEventDialog
};
};
}
Example Data:
{
"Monday": {
"09:00-10:00": {
"subject_name": "Data Structures",
"type": "L",
"location": "C-101"
},
"10:00-11:00": {
"subject_name": "Lab Session",
"type": "P",
"location": "Lab-3"
}
},
"Tuesday": {
"11:00-12:00": {
"subject_name": "Team Meeting",
"type": "C",
"location": "Online",
"isCustom": true
}
}
}
Sources: src/App.tsx28-36 src/components/edit-event-dialog.tsx25-34
Saved Configuration Object
Structure stored in classConfigs:
{
"My CS Configuration": {
year: "2",
batch: "E1",
campus: "62",
selectedSubjects: ["CS501", "CS502", "CS503"],
electiveCount: 3
},
"Fall Semester Setup": {
year: "3",
batch: "E2",
campus: "128",
selectedSubjects: ["IT601", "IT602"],
electiveCount: 2
}
}
Sources: src/App.tsx64-69 src/App.tsx388-390
State Synchronization Patterns
Bidirectional URL Sync
The nuqs library provides automatic bidirectional synchronization between React state and URL parameters:

Benefits:
- Browser back/forward buttons work correctly
- URLs are shareable and bookmarkable
- State survives page refresh via URL
Sources: src/App.tsx231-247 src/App.tsx15-20
Optimistic Updates
Edit operations use optimistic updates - the UI updates immediately while persistence happens asynchronously:

Implementation: src/context/userContextProvider.tsx29-35
Sources: src/components/edit-event-dialog.tsx66-127 src/context/userContextProvider.tsx29-35
Edge Cases and Error Handling
Missing or Corrupted localStorage Data
The application gracefully handles localStorage errors:
Example src/App.tsx85-95:
React.useEffect(() => {
const cached = localStorage.getItem("cachedSchedule");
if (cached) {
try {
const parsed = JSON.parse(cached);
if (parsed && typeof parsed === "object") {
setSchedule(parsed);
}
} catch {} // Silent failure - app continues normally
}
}, []);
Concurrent Modifications
Since the app runs entirely client-side in a single tab, true concurrency issues don't occur. However, if a user opens multiple tabs:
- Each tab maintains independent React state
- localStorage updates from one tab don't automatically sync to others
- The last tab to write wins for localStorage data
Note: There is no mechanism to sync state between tabs. Users working across multiple tabs may experience inconsistent state.
Sources: src/App.tsx85-95 src/context/userContextProvider.tsx18-35
URL Parameter Conflicts
When URL parameters conflict with cached data, the UrlParamsDialog component provides explicit user choice rather than making assumptions:

Sources: src/App.tsx274-359 src/components/url-params-dialog.tsx1-161
Performance Considerations
localStorage Throttling
The application writes to localStorage on every state change without throttling. For most operations this is acceptable, but rapid consecutive edits could potentially cause performance issues.
Current Implementation: No throttling or debouncing
Write Frequency: Every editedSchedule change triggers immediate write
Potential Improvement: Implement debounced writes for edit operations.
Sources: src/context/userContextProvider.tsx29-35
JSON Serialization Overhead
Large timetables with many events require JSON serialization on every persistence operation:
Serialization Points:
- UserContext → localStorage: src/context/userContextProvider.tsx31
- App → localStorage: src/App.tsx107
- Schedule display: Multiple JSON.parse operations
Mitigation: The application uses JSON.parse(JSON.stringify()) to create deep copies, which is performant for the expected data size (typically <50 events per schedule).
Sources: src/App.tsx107-109 src/context/userContextProvider.tsx31