Skip to content

fix: Login sometimes fails to redirect to index, requiring reload#80

Open
nhatt-dev wants to merge 1 commit intomainfrom
fix/login-fail-to-redirect
Open

fix: Login sometimes fails to redirect to index, requiring reload#80
nhatt-dev wants to merge 1 commit intomainfrom
fix/login-fail-to-redirect

Conversation

@nhatt-dev
Copy link
Collaborator

Changes

  • Refactor authContext.tsx, moving session storage logic to a separate hook
  • Upon navigating to /Login, check if there is an active login session and redirect to the index page
  • Also fix "Attempted to navigate before mounting the Root Layout" error by ensuring that the root _layout.tsx file always returns a Stack before any redirection

Testing

  • Manual testing suggests that all login attempts result in the expected behavior of redirecting to the index page

Refactor authContext.tsx, moving session storage logic to a separate hook.
Copy link

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

This PR refactors the authentication flow to fix intermittent login redirect issues. The changes extract session management logic into a reusable hook and ensure proper component mounting before navigation attempts.

Changes:

  • Created a new useStorageState hook to centralize session storage and validation logic
  • Modified auth context to use the new hook and simplified login/logout functions
  • Updated Login page to check for existing sessions and redirect authenticated users
  • Added loading state handling with a new Checking component
  • Simplified backend OAuth callback to use passport's built-in redirect options

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 21 comments.

Show a summary per file
File Description
frontend/hooks/useStorageState.ts New hook that manages session storage, validation, and state synchronization with AsyncStorage
frontend/context/authContext.tsx Refactored to use useStorageState hook, removed redundant session loading logic
frontend/components/Checking.tsx New loading indicator component for auth state checks
frontend/app/_layout.tsx Removed duplicate auth checking logic that caused navigation issues
frontend/app/Login.tsx Added logic to redirect already-authenticated users and changed OAuth redirect mechanism
frontend/app/(tabs)/_layout.tsx Added auth validation on mount and loading state handling
backend/src/server.ts Simplified OAuth callback by using passport's built-in redirect options

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

]);
setUser(userData);
setUserId(userData.id);
setSession({ user: userData, userId: userData.id });
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

There's no validation that userData.id exists before accessing it. If the OAuth response doesn't include an id field, this will result in userId being undefined. Consider adding validation to ensure the user data has the expected structure before storing it.

Suggested change
setSession({ user: userData, userId: userData.id });
const userId =
userData && typeof userData === "object" && "id" in userData
? (userData as { id: string | number }).id
: null;
setSession({ user: userData, userId });

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +26
console.log("useStorageState.tsx: set isLoading to false");
console.table(action);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

Using console.log and console.table in production code is not recommended. These debug statements should either be removed or replaced with a proper logging framework that can be disabled in production builds.

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +30
useEffect(() => {
fetch(`http://localhost:${BACKEND_PORT}/auth/me`, {
credentials: "include",
})
.then((response) => {
if (response.ok) {
response.json().then(login);
}
})
.catch((error) => {
console.error("Error checking auth:", error);
});
}, []);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

This useEffect creates a race condition with the session validation already happening in useStorageState. When the TabLayout mounts, it fetches /auth/me and calls login(), which triggers another session storage update. This is redundant since useStorageState already validates the session on mount. Consider removing this effect and relying on the centralized session management in useStorageState.

Copilot uses AI. Check for mistakes.
console.table(value);
setState(value);
setStorageItem(value);
}, []);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The setValue callback is missing setState in its dependency array. This should be included to ensure the callback has access to the current state setter.

Suggested change
}, []);
}, [setState, setStorageItem]);

Copilot uses AI. Check for mistakes.
await AsyncStorage.removeItem("userId");
setUser(null);
setUserId(null);
setSession({ user: null, userId: null });
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The logout function sets the session to { user: null, userId: null } which is inconsistent with how setStorageItem handles null values. Looking at setStorageItem, passing null removes the items from storage, but passing an object with null properties stores those null values. Consider passing null directly to setSession instead of an object with null properties for consistency with the intended logout behavior.

Suggested change
setSession({ user: null, userId: null });
setSession(null);

Copilot uses AI. Check for mistakes.
if (!res.ok) throw new Error("Session invalid");
return { user: userData, userId: userData.id };
} catch (err) {
await AsyncStorage.clear();
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

When session validation fails, AsyncStorage.clear() is called, which removes ALL stored data including potential data unrelated to authentication. This is overly aggressive and could lead to data loss. Consider only removing the specific auth-related keys ('user' and 'userId') instead.

Suggested change
await AsyncStorage.clear();
await Promise.all(
authUserSessionKeys.map((key) => AsyncStorage.removeItem(key)),
);

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +7
export interface AuthUserSession {
user: any | null;
userId: any | null;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

Using any type for the user object defeats TypeScript's type safety benefits. Consider defining a proper User interface or type that describes the expected structure of the user data, including at minimum the id property that's being accessed.

Copilot uses AI. Check for mistakes.
Comment on lines +83 to +84
console.log("userStorageState.tsx: the setter is ran");
console.table(value);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

Using console.log and console.table in production code is not recommended. These debug statements should either be removed or replaced with a proper logging framework that can be disabled in production builds.

Copilot uses AI. Check for mistakes.
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.

1 participant