From fa6a4df5f2aa6df7f6de08797cb4cf3d4cc413dd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Jan 2026 06:15:08 +0000 Subject: [PATCH 1/2] fix: Remove AntD responsive observer usage to prevent React update loop Replaced AntD Row/Col components with responsive props (xs, sm) with CSS-based flex layouts using media queries in queue-list and queue-list-item. Also removed unused ClimbInfoColumn component that was using Grid.useBreakpoint(). The AntD responsiveObserver was causing "Maximum update depth exceeded" errors when dispatching breakpoint changes to multiple subscribed components, triggering cascading re-renders that resulted in infinite loops. --- .../climb-info/climb-info-drawer.tsx | 19 ---- .../queue-control/queue-list-item.module.css | 66 +++++++++++++ .../queue-control/queue-list-item.tsx | 23 ++--- .../queue-control/queue-list.module.css | 95 +++++++++++++++++++ .../components/queue-control/queue-list.tsx | 35 +++---- 5 files changed, 191 insertions(+), 47 deletions(-) delete mode 100644 packages/web/app/components/climb-info/climb-info-drawer.tsx create mode 100644 packages/web/app/components/queue-control/queue-list-item.module.css create mode 100644 packages/web/app/components/queue-control/queue-list.module.css diff --git a/packages/web/app/components/climb-info/climb-info-drawer.tsx b/packages/web/app/components/climb-info/climb-info-drawer.tsx deleted file mode 100644 index ba745b70..00000000 --- a/packages/web/app/components/climb-info/climb-info-drawer.tsx +++ /dev/null @@ -1,19 +0,0 @@ -'use client'; - -import React from 'react'; -import ClimbInfo from './climb-info'; -import { Grid } from 'antd'; - -const { useBreakpoint } = Grid; - -const ClimbInfoColumn = () => { - const screens = useBreakpoint(); - - // Sidebar for desktop view - const desktopSidebar = ; - - // Conditionally render based on screen size - return screens.md ? desktopSidebar : null; -}; - -export default ClimbInfoColumn; diff --git a/packages/web/app/components/queue-control/queue-list-item.module.css b/packages/web/app/components/queue-control/queue-list-item.module.css new file mode 100644 index 00000000..1a435199 --- /dev/null +++ b/packages/web/app/components/queue-control/queue-list-item.module.css @@ -0,0 +1,66 @@ +/* Flex row to replace AntD Row/Col with responsive props */ +.itemRow { + display: flex; + width: 100%; + gap: 8px; + align-items: center; + flex-wrap: nowrap; +} + +/* Thumbnail column: xs=6 (25%), sm=5 (20.83%) */ +.thumbnailCol { + flex: 0 0 25%; + max-width: 25%; +} + +@media (min-width: 576px) { + .thumbnailCol { + flex: 0 0 20.83%; + max-width: 20.83%; + } +} + +/* Title column without user: xs=15 (62.5%), sm=17 (70.83%) */ +.titleCol { + flex: 0 0 62.5%; + max-width: 62.5%; +} + +@media (min-width: 576px) { + .titleCol { + flex: 0 0 70.83%; + max-width: 70.83%; + } +} + +/* Title column with user: xs=13 (54.17%), sm=15 (62.5%) */ +.titleColWithUser { + flex: 0 0 54.17%; + max-width: 54.17%; +} + +@media (min-width: 576px) { + .titleColWithUser { + flex: 0 0 62.5%; + max-width: 62.5%; + } +} + +/* Avatar column: xs=2 (8.33%), sm=2 (8.33%) */ +.avatarCol { + flex: 0 0 8.33%; + max-width: 8.33%; +} + +/* Menu button column: xs=3 (12.5%), sm=2 (8.33%) */ +.menuCol { + flex: 0 0 12.5%; + max-width: 12.5%; +} + +@media (min-width: 576px) { + .menuCol { + flex: 0 0 8.33%; + max-width: 8.33%; + } +} diff --git a/packages/web/app/components/queue-control/queue-list-item.tsx b/packages/web/app/components/queue-control/queue-list-item.tsx index ee0f3a53..5643e636 100644 --- a/packages/web/app/components/queue-control/queue-list-item.tsx +++ b/packages/web/app/components/queue-control/queue-list-item.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'; import { useRouter } from 'next/navigation'; -import { Row, Col, Avatar, Tooltip, Dropdown, Button } from 'antd'; +import { Avatar, Tooltip, Dropdown, Button } from 'antd'; import { CheckOutlined, CloseOutlined, UserOutlined, DeleteOutlined, MoreOutlined, InfoCircleOutlined, AppstoreOutlined } from '@ant-design/icons'; import { BoardDetails, ClimbUuid, Climb } from '@/app/lib/types'; import { draggable, dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter'; @@ -15,6 +15,7 @@ import ClimbTitle from '../climb-card/climb-title'; import { useBoardProvider } from '../board-provider/board-provider-context'; import { themeTokens } from '@/app/theme/theme-config'; import { constructClimbViewUrl, constructClimbViewUrlWithSlugs, parseBoardRouteParams, constructClimbInfoUrl } from '@/app/lib/url-utils'; +import styles from './queue-list-item.module.css'; type QueueListItemProps = { item: ClimbQueueItem; @@ -327,29 +328,29 @@ const QueueListItem: React.FC = ({ }} onDoubleClick={() => setCurrentClimbQueueItem(item)} > - - +
+
- - +
+
} /> - +
{item.addedByUser && ( - +
} /> - +
)} - +
= ({ >
+
{closestEdge && } diff --git a/packages/web/app/components/queue-control/queue-list.module.css b/packages/web/app/components/queue-control/queue-list.module.css new file mode 100644 index 00000000..bc5d4253 --- /dev/null +++ b/packages/web/app/components/queue-control/queue-list.module.css @@ -0,0 +1,95 @@ +/* Flex row to replace AntD Row/Col with responsive props */ +.suggestionRow { + display: flex; + width: 100%; + gap: 8px; + align-items: center; + flex-wrap: nowrap; +} + +/* Thumbnail column: xs=6 (25%), sm=5 (20.83%) */ +.thumbnailCol { + flex: 0 0 25%; + max-width: 25%; +} + +@media (min-width: 576px) { + .thumbnailCol { + flex: 0 0 20.83%; + max-width: 20.83%; + } +} + +/* Title column: xs=15 (62.5%), sm=17 (70.83%) */ +.titleCol { + flex: 0 0 62.5%; + max-width: 62.5%; +} + +@media (min-width: 576px) { + .titleCol { + flex: 0 0 70.83%; + max-width: 70.83%; + } +} + +/* Button column: xs=3 (12.5%), sm=2 (8.33%) */ +.buttonCol { + flex: 0 0 12.5%; + max-width: 12.5%; +} + +@media (min-width: 576px) { + .buttonCol { + flex: 0 0 8.33%; + max-width: 8.33%; + } +} + +/* Skeleton row for loading state */ +.skeletonRow { + display: flex; + width: 100%; + gap: 8px; + align-items: center; + flex-wrap: nowrap; +} + +/* Skeleton thumbnail column */ +.skeletonThumbnailCol { + flex: 0 0 25%; + max-width: 25%; +} + +@media (min-width: 576px) { + .skeletonThumbnailCol { + flex: 0 0 20.83%; + max-width: 20.83%; + } +} + +/* Skeleton title column */ +.skeletonTitleCol { + flex: 0 0 62.5%; + max-width: 62.5%; +} + +@media (min-width: 576px) { + .skeletonTitleCol { + flex: 0 0 70.83%; + max-width: 70.83%; + } +} + +/* Skeleton button column */ +.skeletonButtonCol { + flex: 0 0 12.5%; + max-width: 12.5%; +} + +@media (min-width: 576px) { + .skeletonButtonCol { + flex: 0 0 8.33%; + max-width: 8.33%; + } +} diff --git a/packages/web/app/components/queue-control/queue-list.tsx b/packages/web/app/components/queue-control/queue-list.tsx index f355db4b..05edee1a 100644 --- a/packages/web/app/components/queue-control/queue-list.tsx +++ b/packages/web/app/components/queue-control/queue-list.tsx @@ -1,6 +1,6 @@ 'use client'; import React, { useEffect, useState, useCallback, useRef } from 'react'; -import { Divider, Row, Col, Button, Flex, Drawer, Space, Typography, Skeleton } from 'antd'; +import { Divider, Button, Flex, Drawer, Space, Typography, Skeleton } from 'antd'; import { PlusOutlined, LoginOutlined } from '@ant-design/icons'; import { useQueueContext } from '../graphql-queue'; import { Climb, BoardDetails } from '@/app/lib/types'; @@ -15,6 +15,7 @@ import { SUGGESTIONS_THRESHOLD } from '../board-page/constants'; import { useBoardProvider } from '../board-provider/board-provider-context'; import { LogAscentDrawer } from '../logbook/log-ascent-drawer'; import AuthModal from '../auth/auth-modal'; +import styles from './queue-list.module.css'; const { Text, Paragraph } = Typography; @@ -169,22 +170,22 @@ const QueueList: React.FC = ({ boardDetails, onClimbNavigate }) borderBottom: `1px solid ${themeTokens.neutral[200]}`, }} > - - +
+
- - +
+
- - +
+
+
))} @@ -198,17 +199,17 @@ const QueueList: React.FC = ({ boardDetails, onClimbNavigate }) {(isFetchingClimbs || isFetchingNextPage) && ( {[1, 2, 3].map((i) => ( - - +
+
- - +
+
- - +
+
- - +
+
))}
)} From ac98ea5c00784189603184cf45c46cc00fca7050 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 3 Jan 2026 06:16:05 +0000 Subject: [PATCH 2/2] chore: Update package-lock.json --- package-lock.json | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index a8af944d..ab544795 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4563,7 +4563,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "playwright": "1.57.0" @@ -7327,7 +7327,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -8077,7 +8077,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -10233,7 +10233,7 @@ "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -10713,7 +10713,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "playwright-core": "1.57.0" @@ -10732,7 +10732,7 @@ "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -10964,7 +10964,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10984,7 +10983,6 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", - "dev": true, "license": "MIT", "dependencies": { "scheduler": "^0.27.0" @@ -11284,7 +11282,6 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "dev": true, "license": "MIT" }, "node_modules/scroll-into-view-if-needed": {