Skip to content

Fix localStorage race conditions and error handling in user settings#274

Merged
devakesu merged 2 commits into1.4.8from
copilot/sub-pr-272
Feb 6, 2026
Merged

Fix localStorage race conditions and error handling in user settings#274
devakesu merged 2 commits into1.4.8from
copilot/sub-pr-272

Conversation

Copy link
Contributor

Copilot AI commented Feb 6, 2026

Addresses code review feedback on user settings persistence: localStorage operations could fail in restricted environments, async session validation had race conditions, and dead code remained from previous refactoring.

Changes

  • Wrap localStorage writes in try/catch (onMutate): Storage access throws in private mode; catch and log without failing the mutation
  • Fix validateActiveSession race condition: Remove stale boolean parameter, read isMounted from closure after getSession() completes to prevent cross-user leakage
  • Remove unused hasCompletedInitialFetchRef: Dead state from earlier sync logic iteration
  • Use getSession() over getUser() (CourseCard): Avoid network request on mount; read cached session
// Before: stale parameter allows state updates after unmount
const validateActiveSession = async (userId: string, isComponentMounted: boolean) => {
  if (!isComponentMounted) return false;
  const { data: { session } } = await supabase.auth.getSession();
  return !!(session?.user.id === userId && isComponentMounted); // stale
};

// After: closure reflects current mount state
const validateActiveSession = async (userId: string) => {
  if (!isMounted) return false; // fresh check
  const { data: { session } } = await supabase.auth.getSession();
  return !!(session?.user.id === userId && isMounted); // fresh recheck
};

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

…ove dead code

Co-authored-by: devakesu <61821107+devakesu@users.noreply.github.com>
Copilot AI changed the title [WIP] Update localStorage handling for user-specific settings Fix localStorage race conditions and error handling in user settings Feb 6, 2026
Copilot AI requested a review from devakesu February 6, 2026 18:52
@devakesu devakesu marked this pull request as ready for review February 6, 2026 18:53
Copilot AI review requested due to automatic review settings February 6, 2026 18:53
@devakesu devakesu merged commit 2778945 into 1.4.8 Feb 6, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Improves robustness of user settings persistence and session validation by hardening storage interactions and removing stale/racy logic around Supabase session checks.

Changes:

  • Wraps localStorage writes in try/catch during optimistic updates to avoid failing mutations in restricted storage environments.
  • Updates validateActiveSession to avoid stale mount-state parameters and reduce race conditions during async session checks.
  • Switches CourseCard from getUser() to getSession() to prefer cached session access and reduce unnecessary network calls.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/providers/user-settings.ts Adds guarded localStorage writes in onMutate, refactors session validation helper, and removes dead initial-fetch ref logic.
src/components/attendance/course-card.tsx Uses getSession() instead of getUser() when deriving the scoped localStorage key for bunk calc UI state.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +199 to +211
try {
if (newSettings.bunk_calculator_enabled !== undefined) {
localStorage.setItem(`showBunkCalc_${userId}`, newSettings.bunk_calculator_enabled.toString());
window.dispatchEvent(new CustomEvent("bunkCalcToggle", { detail: newSettings.bunk_calculator_enabled }));
}
if (newSettings.target_percentage !== undefined) {
const normalizedTarget = normalizeTarget(newSettings.target_percentage);
localStorage.setItem(`targetPercentage_${userId}`, normalizedTarget.toString());
}
} catch (error) {
// Storage access may throw in private mode or when disabled
// Log error but don't fail the mutation - DB update can still proceed
logger.dev("Failed to write to localStorage in onMutate", { error });
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The try block wraps both localStorage.setItem and the bunkCalcToggle event dispatch. In environments where setItem throws (private mode / storage disabled), the event will never dispatch, so listeners (e.g. CourseCard) won’t update even though the optimistic cache update succeeded. Consider limiting the try/catch to just the localStorage calls and dispatching the event regardless (or dispatching in a finally after computing the new value).

Suggested change
try {
if (newSettings.bunk_calculator_enabled !== undefined) {
localStorage.setItem(`showBunkCalc_${userId}`, newSettings.bunk_calculator_enabled.toString());
window.dispatchEvent(new CustomEvent("bunkCalcToggle", { detail: newSettings.bunk_calculator_enabled }));
}
if (newSettings.target_percentage !== undefined) {
const normalizedTarget = normalizeTarget(newSettings.target_percentage);
localStorage.setItem(`targetPercentage_${userId}`, normalizedTarget.toString());
}
} catch (error) {
// Storage access may throw in private mode or when disabled
// Log error but don't fail the mutation - DB update can still proceed
logger.dev("Failed to write to localStorage in onMutate", { error });
if (newSettings.bunk_calculator_enabled !== undefined) {
try {
localStorage.setItem(`showBunkCalc_${userId}`, newSettings.bunk_calculator_enabled.toString());
} catch (error) {
// Storage access may throw in private mode or when disabled
// Log error but don't fail the mutation - DB update can still proceed
logger.dev("Failed to write bunk_calculator_enabled to localStorage in onMutate", { error });
}
// Always notify listeners about bunk calculator toggle, even if localStorage fails
window.dispatchEvent(new CustomEvent("bunkCalcToggle", { detail: newSettings.bunk_calculator_enabled }));
}
if (newSettings.target_percentage !== undefined) {
const normalizedTarget = normalizeTarget(newSettings.target_percentage);
try {
localStorage.setItem(`targetPercentage_${userId}`, normalizedTarget.toString());
} catch (error) {
// Storage access may throw in private mode or when disabled
// Log error but don't fail the mutation - DB update can still proceed
logger.dev("Failed to write target_percentage to localStorage in onMutate", { error });
}

Copilot uses AI. Check for mistakes.
devakesu added a commit that referenced this pull request Feb 6, 2026
* feat: update localStorage handling for user-specific settings

- Upgrade glob package to version 13.0.1.
- Modify CourseCard component to use user ID for scoped localStorage keys for the bunk calculator setting.
- Update useUserSettings hook to sync user settings to localStorage using user-specific keys.
- Implement cleanup of legacy localStorage keys after migrating to user-scoped keys.
- Ensure session validation checks are performed correctly to prevent race conditions during user logout.

* Fix session validation and null userId in localStorage operations (#273)

* Initial plan

* fix: address PR review comments for localStorage and session validation

- Fix null userId in onMutate by getting session directly instead of using ref
- Add userId validation in validateActiveSession to prevent cross-user leakage
- Wrap getSession() calls in try/catch to handle transient errors gracefully
- Add isMounted guard in CourseCard to prevent state updates after unmount
- Replace dynamic import with static import for Supabase client
- Use logger.dev instead of console.debug for consistency

Co-authored-by: devakesu <61821107+devakesu@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devakesu <61821107+devakesu@users.noreply.github.com>

* Fix localStorage race conditions and error handling in user settings (#274)

* Initial plan

* Address review comments: add error handling, fix race conditions, remove dead code

Co-authored-by: devakesu <61821107+devakesu@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: devakesu <61821107+devakesu@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
@devakesu devakesu deleted the copilot/sub-pr-272 branch February 7, 2026 13:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants