fix: notifications pagination + dropdown updates#86
Conversation
|
Hi @tarinagarwal 👋 Just a gentle follow-up regarding this PR. I’m happy to iterate or make updates as needed. |
|
@Anshpujara5 Hey! ill review this pr asap, sorry for the delay |
There was a problem hiding this comment.
Changes Requested 🐈
This PR adds pagination to the notifications system, improving performance by loading notifications in batches and enhancing the UI with a Load More button. Review found improvements in backend pagination and UI, but raised concerns about input validation, potential race conditions, inefficient client-side data handling, lack of test coverage, and missing documentation.
There are a few things I'd like to see addressed before we merge this:
Before merging
- Add input validation and sanitization for notification IDs and pagination parameters to improve security.
- Refactor NotificationDropdown component to improve readability and maintainability, including adding JSDoc comments and reducing complexity.
- Optimize client-side notification handling by using efficient data structures for duplicate detection and read status updates, and add test coverage for new features.
Findings breakdown (35 total)
3 high / 12 medium / 15 low / 5 info
Confidence: 90%
🔗 View Full Review Report — detailed findings, severity breakdown, and agent analysis
Reviewed by Looks Good To Meow — AI-powered code review
|
|
||
| const NotificationDropdown: React.FC = () => { | ||
| const [notifications, setNotifications] = useState<Notification[]>([]); | ||
| const [notifications, setNotifications] = useState<AppNotification[]>([]); |
There was a problem hiding this comment.
No action needed; pagination is correctly implemented for notifications.
performance
| where: { userId: req.user.id }, | ||
| }), | ||
| ]); | ||
| const [notifications, total,unreadCount] = await Promise.all([ |
There was a problem hiding this comment.
No action needed; pagination is properly implemented in the notifications API.
performance
|
|
||
| const NotificationDropdown: React.FC = () => { | ||
| const [notifications, setNotifications] = useState<Notification[]>([]); | ||
| const [notifications, setNotifications] = useState<AppNotification[]>([]); |
There was a problem hiding this comment.
Add a JSDoc comment above the 'NotificationDropdown' component explaining its functionality, props (if any), and what it returns.
documentation
|
|
||
| const NotificationDropdown: React.FC = () => { | ||
| const [notifications, setNotifications] = useState<Notification[]>([]); | ||
| const [notifications, setNotifications] = useState<AppNotification[]>([]); |
There was a problem hiding this comment.
🔍 Medium — The 'notifications' state is updated inside the socket 'new_notification' event handler using a functional update, but the 'unreadCount' state is updated separately inside the same handler. This can cause race conditions or inconsistent state if multiple notifications arrive rapidly.
Combine the updates to 'notifications' and 'unreadCount' into a single state update or use a reducer to manage both states atomically.
bugs
| where: { userId: req.user.id }, | ||
| }), | ||
| ]); | ||
| const [notifications, total,unreadCount] = await Promise.all([ |
There was a problem hiding this comment.
🔍 Medium — Notification read status update endpoint checks that the notification belongs to the authenticated user before allowing update, which is good. However, the notification ID is taken directly from the URL parameter without validation or sanitization. While Prisma ORM likely handles parameterization, ensure that the ID is validated to prevent injection or malformed input.
Validate and sanitize the notification ID parameter before using it in database queries. Use proper type checks and reject invalid IDs early.
security
| {unreadCount > 0 && ( | ||
| <span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-5 w-5 flex items-center justify-center animate-pulse"> | ||
| {unreadCount > 9 ? "9+" : unreadCount} | ||
| <span className="absolute -top-1 -right-1 z-[9999] bg-red-500 text-white text-[10px] font-semibold rounded-full h-5 min-w-5 px-1 flex items-center justify-center"> |
There was a problem hiding this comment.
💡 Suggestion — The unread count badge shows the exact number even if it exceeds 9, whereas previously it showed '9+'. This may cause UI inconsistency or overflow.
Consider limiting the displayed count to '9+' or a max value to maintain consistent badge size and appearance.
best-practices
| notifications.map((notification) => ( | ||
| <div | ||
| key={notification.id} | ||
| key={getNotifId(notification)} |
There was a problem hiding this comment.
💡 Suggestion — Notification list items use getNotifId(notification) as key, which may cause issues if IDs are not unique or stable.
Ensure that notification IDs are unique and stable; otherwise, consider a more stable key or include additional identifiers.
best-practices
| </p> | ||
| </span> | ||
| {!notification.is_read && ( | ||
| <button |
There was a problem hiding this comment.
💡 Suggestion — The 'Mark as read' button inside each notification item stops propagation on click but does not prevent default, which may cause unexpected behavior in some browsers.
Add e.preventDefault() along with e.stopPropagation() to fully prevent event bubbling and default actions.
best-practices
| {page < totalPages && ( | ||
| <div className="p-3 text-center"> | ||
| <button | ||
| onClick={() => fetchNotifications(page + 1)} |
There was a problem hiding this comment.
💡 Suggestion — The 'Load More' button for pagination does not have aria-label or accessible name, which may affect accessibility.
Add aria-label or descriptive text for screen readers to improve accessibility.
best-practices
| socket.on("new_notification", (notification: Notification) => { | ||
| setNotifications((prev) => [notification, ...prev]); | ||
| setUnreadCount((prev) => prev + 1); | ||
| const handler = (notification: AppNotification) => { |
There was a problem hiding this comment.
💡 Suggestion — The variable 'n' in the getNotifId function and in the some() callback is typed as 'any', which reduces type safety and clarity.
Define a proper type for the notification parameter in getNotifId and the some() callback to improve type safety and readability.
readability
Description
Adds pagination to the notifications system so notifications load in batches instead of all at once.
Backend pagination parameters (
page,limit) are verified and used correctly, and the frontend notification dropdown now supports incremental loading via a Load More button. This improves performance and provides a smoother user experience for users with large notification histories.Type of Change
Related Issues
Closes #13
Testing
npm run build) passes successfullyScreenshots
Checklist