Skip to content

Conversation

@devksingh4
Copy link
Member

@devksingh4 devksingh4 commented Nov 6, 2025

Summary by CodeRabbit

New Features

  • Item filter selections in the ticket scanning interface are now reflected in the URL, enabling shareable links with pre-selected items
  • URL parameters are validated against loaded data, with invalid selections automatically cleared for consistency

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds URL state management for item filtering using useSearchParams. Implements useCallback hooks to stabilize API function references and dependencies. Reads initial itemId from URL, validates against loaded items, and synchronizes it with UI state through a memoized change handler.

Changes

Cohort / File(s) Summary
URL State Synchronization
src/ui/pages/tickets/ScanTickets.page.tsx
Integrates URL parameter management for item filtering; wraps API functions and utility functions in useCallback for reference stability; reads and validates initial itemId from URL on component mount; adds memoized handleItemFilterChange to sync item selection with URL state; updates effect dependencies to include search parameter handlers.

Sequence Diagram

sequenceDiagram
    participant Component as ScanTickets Component
    participant URL as URL Params
    participant State as Local State
    participant API as API/Data

    rect rgb(200, 220, 255)
    Note over Component,API: Initialization
    Component->>URL: Read searchParams (itemId)
    Component->>API: Load organizations & ticket items
    API-->>Component: Items loaded
    Component->>State: Validate itemId against items
    alt itemId valid
        Component->>State: Initialize selectedItemFilter
        Component->>URL: Keep itemId param
    else itemId invalid
        Component->>State: Clear selectedItemFilter
        Component->>URL: Clear itemId param
    end
    end

    rect rgb(220, 240, 220)
    Note over Component,State: User Interaction
    Component->>Component: User selects item
    Component->>Component: handleItemFilterChange triggered
    Component->>State: Update selectedItemFilter
    Component->>URL: Sync itemId to URL params
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Single-file change with straightforward URL state synchronization logic
  • Consistent pattern of adding useCallback wrappers and URL parameter handling
  • URL validation logic is clear and follows a standard pattern
  • Recommended focus: Verify URL parameter validation logic correctly handles edge cases and that dependency arrays in useEffect and useCallback are complete

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions 'iCard swiper' but the actual changes focus on URL state management and memoized callbacks in ticket scanning functionality, which are unrelated concepts. Update the title to reflect the actual changes, such as 'Add URL state management for ticket item filtering in ScanTickets' or similar.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dsingh14/new

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 6, 2025

💰 Infracost report

Monthly estimate generated

This comment will be updated when code changes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 3bdd0f4 and 9fa360c.

📒 Files selected for processing (1)
  • src/ui/pages/tickets/ScanTickets.page.tsx (8 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
src/ui/pages/tickets/ScanTickets.page.tsx

[error] 171-171: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 178-178: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 185-185: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 197-197: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🪛 GitHub Actions: Required Reviews
src/ui/pages/tickets/ScanTickets.page.tsx

[error] 1-1: Requirement "Base Requirement" is not satisfied by the existing reviews.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Application
  • GitHub Check: Run Unit Tests
🔇 Additional comments (6)
src/ui/pages/tickets/ScanTickets.page.tsx (6)

20-23: LGTM! Clean import additions.

The addition of useCallback and useSearchParams aligns with the new URL state management and callback stabilization features.


118-119: LGTM! Proper URL state initialization.

The useSearchParams hook is correctly initialized and the initial itemId is properly read from the URL to initialize the filter state.

Also applies to: 151-154


364-375: LGTM! Robust URL validation.

The validation logic ensures the itemId from the URL exists in the loaded items, and properly clears invalid values. Using replace: true prevents polluting browser history.


832-843: LGTM! Well-implemented memoized handler.

The memoized handleItemFilterChange properly synchronizes both local state and URL parameters, with setSearchParams as a stable dependency.


864-864: LGTM! Proper usage of memoized handler.

The Select component now uses the memoized handler, improving performance by preventing unnecessary re-renders.


394-394: No action required.

The component is used without any props in src/ui/Router.tsx (line 189), so the concern about prop function stability is not applicable. The dependencies on line 394 are correct for the current usage pattern. The component works as intended with the optional props mechanism.

Comment on lines 169 to +228
const getOrganizations =
getOrganizationsProp ||
(async () => {
useCallback(async () => {
const response = await api.get("/api/v1/organizations");
return response.data;
});
}, [api]);

const getTicketItems =
getTicketItemsProp ||
(async () => {
useCallback(async () => {
const response = await api.get("/api/v1/tickets");
return response.data;
});
}, [api]);

const getPurchasesByEmail =
getPurchasesByEmailProp ||
(async (email: string) => {
const response = await api.get<PurchasesByEmailResponse>(
`/api/v1/tickets/purchases/${encodeURIComponent(email)}`,
);
return response.data;
});
useCallback(
async (email: string) => {
const response = await api.get<PurchasesByEmailResponse>(
`/api/v1/tickets/purchases/${encodeURIComponent(email)}`,
);
return response.data;
},
[api],
);

const checkInTicket =
checkInTicketProp ||
(async (data: any) => {
const response = await api.post(
`/api/v1/tickets/checkIn`,
recursiveToCamel(data),
);
return response.data as APIResponseSchema;
});
useCallback(
async (data: any) => {
const response = await api.post(
`/api/v1/tickets/checkIn`,
recursiveToCamel(data),
);
return response.data as APIResponseSchema;
},
[api],
);

const getEmailFromUINDefault = async (uin: string): Promise<string> => {
try {
const response = await api.post(`/api/v1/users/findUserByUin`, { uin });
return response.data.email;
} catch (error: any) {
const samp = new ValidationError({
message: "Failed to convert UIN to email.",
});
if (
error.response?.status === samp.httpStatusCode &&
error.response?.data.id === samp.id
) {
const validationData = error.response.data;
throw new ValidationError(validationData.message || samp.message);
const getEmailFromUINDefault = useCallback(
async (uin: string): Promise<string> => {
try {
const response = await api.post(`/api/v1/users/findUserByUin`, { uin });
return response.data.email;
} catch (error: any) {
const samp = new ValidationError({
message: "Failed to convert UIN to email.",
});
if (
error.response?.status === samp.httpStatusCode &&
error.response?.data.id === samp.id
) {
const validationData = error.response.data;
throw new ValidationError(validationData.message || samp.message);
}
throw error;
}
throw error;
}
};
},
[api],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix conditional hook calls - violates Rules of Hooks.

The pattern const fn = propFn || useCallback(...) violates the Rules of Hooks because the OR operator short-circuits. When propFn is truthy, useCallback is never called, changing hook call order between renders.

Apply this pattern to fix all instances:

-  const getOrganizations =
-    getOrganizationsProp ||
-    useCallback(async () => {
+  const getOrganizationsDefault = useCallback(async () => {
       const response = await api.get("/api/v1/organizations");
       return response.data;
     }, [api]);
+  const getOrganizations = getOrganizationsProp || getOrganizationsDefault;

-  const getTicketItems =
-    getTicketItemsProp ||
-    useCallback(async () => {
+  const getTicketItemsDefault = useCallback(async () => {
       const response = await api.get("/api/v1/tickets");
       return response.data;
     }, [api]);
+  const getTicketItems = getTicketItemsProp || getTicketItemsDefault;

-  const getPurchasesByEmail =
-    getPurchasesByEmailProp ||
-    useCallback(
-      async (email: string) => {
+  const getPurchasesByEmailDefault = useCallback(
+    async (email: string) => {
         const response = await api.get<PurchasesByEmailResponse>(
           `/api/v1/tickets/purchases/${encodeURIComponent(email)}`,
         );
         return response.data;
       },
       [api],
     );
+  const getPurchasesByEmail = getPurchasesByEmailProp || getPurchasesByEmailDefault;

-  const checkInTicket =
-    checkInTicketProp ||
-    useCallback(
-      async (data: any) => {
+  const checkInTicketDefault = useCallback(
+    async (data: any) => {
         const response = await api.post(
           `/api/v1/tickets/checkIn`,
           recursiveToCamel(data),
         );
         return response.data as APIResponseSchema;
       },
       [api],
     );
+  const checkInTicket = checkInTicketProp || checkInTicketDefault;

Note: getEmailFromUINDefault at lines 208-228 is already correct since it's always called unconditionally.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const getOrganizations =
getOrganizationsProp ||
(async () => {
useCallback(async () => {
const response = await api.get("/api/v1/organizations");
return response.data;
});
}, [api]);
const getTicketItems =
getTicketItemsProp ||
(async () => {
useCallback(async () => {
const response = await api.get("/api/v1/tickets");
return response.data;
});
}, [api]);
const getPurchasesByEmail =
getPurchasesByEmailProp ||
(async (email: string) => {
const response = await api.get<PurchasesByEmailResponse>(
`/api/v1/tickets/purchases/${encodeURIComponent(email)}`,
);
return response.data;
});
useCallback(
async (email: string) => {
const response = await api.get<PurchasesByEmailResponse>(
`/api/v1/tickets/purchases/${encodeURIComponent(email)}`,
);
return response.data;
},
[api],
);
const checkInTicket =
checkInTicketProp ||
(async (data: any) => {
const response = await api.post(
`/api/v1/tickets/checkIn`,
recursiveToCamel(data),
);
return response.data as APIResponseSchema;
});
useCallback(
async (data: any) => {
const response = await api.post(
`/api/v1/tickets/checkIn`,
recursiveToCamel(data),
);
return response.data as APIResponseSchema;
},
[api],
);
const getEmailFromUINDefault = async (uin: string): Promise<string> => {
try {
const response = await api.post(`/api/v1/users/findUserByUin`, { uin });
return response.data.email;
} catch (error: any) {
const samp = new ValidationError({
message: "Failed to convert UIN to email.",
});
if (
error.response?.status === samp.httpStatusCode &&
error.response?.data.id === samp.id
) {
const validationData = error.response.data;
throw new ValidationError(validationData.message || samp.message);
const getEmailFromUINDefault = useCallback(
async (uin: string): Promise<string> => {
try {
const response = await api.post(`/api/v1/users/findUserByUin`, { uin });
return response.data.email;
} catch (error: any) {
const samp = new ValidationError({
message: "Failed to convert UIN to email.",
});
if (
error.response?.status === samp.httpStatusCode &&
error.response?.data.id === samp.id
) {
const validationData = error.response.data;
throw new ValidationError(validationData.message || samp.message);
}
throw error;
}
throw error;
}
};
},
[api],
);
const getOrganizationsDefault = useCallback(async () => {
const response = await api.get("/api/v1/organizations");
return response.data;
}, [api]);
const getOrganizations = getOrganizationsProp || getOrganizationsDefault;
const getTicketItemsDefault = useCallback(async () => {
const response = await api.get("/api/v1/tickets");
return response.data;
}, [api]);
const getTicketItems = getTicketItemsProp || getTicketItemsDefault;
const getPurchasesByEmailDefault = useCallback(
async (email: string) => {
const response = await api.get<PurchasesByEmailResponse>(
`/api/v1/tickets/purchases/${encodeURIComponent(email)}`,
);
return response.data;
},
[api],
);
const getPurchasesByEmail = getPurchasesByEmailProp || getPurchasesByEmailDefault;
const checkInTicketDefault = useCallback(
async (data: any) => {
const response = await api.post(
`/api/v1/tickets/checkIn`,
recursiveToCamel(data),
);
return response.data as APIResponseSchema;
},
[api],
);
const checkInTicket = checkInTicketProp || checkInTicketDefault;
const getEmailFromUINDefault = useCallback(
async (uin: string): Promise<string> => {
try {
const response = await api.post(`/api/v1/users/findUserByUin`, { uin });
return response.data.email;
} catch (error: any) {
const samp = new ValidationError({
message: "Failed to convert UIN to email.",
});
if (
error.response?.status === samp.httpStatusCode &&
error.response?.data.id === samp.id
) {
const validationData = error.response.data;
throw new ValidationError(validationData.message || samp.message);
}
throw error;
}
},
[api],
);
🧰 Tools
🪛 Biome (2.1.2)

[error] 171-171: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 178-178: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 185-185: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)


[error] 197-197: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

@devksingh4 devksingh4 merged commit e4374a4 into main Nov 6, 2025
11 of 13 checks passed
@devksingh4 devksingh4 deleted the dsingh14/new branch November 6, 2025 21:23
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