Skip to content

Commit d73bffe

Browse files
rhamiltoclaude
andcommitted
CONSOLE-4990: Migrate from history object to React Router v6/v7 hooks
Replace direct history object usage with React Router v6/v7 compatible hooks to prepare for react-router v7 upgrade. ## Key Changes ### New Hook API (public/components/utils/router.ts) **useQueryParamsMutator()** - Returns 7 query parameter mutation functions using useSearchParams() - Preserves URL hash and location state during updates - Only triggers updates when values actually change (optimization) - Uses replace mode to avoid polluting browser history **useRouterPush()** - Replacement for direct history.push() calls - Uses useNavigate() from react-router-dom-v5-compat ### Migration Pattern Components now import and use hooks instead of utility functions: **Before:** ```typescript import { setQueryArgument } from '@console/internal/components/utils/router'; const MyComponent = () => { const onClick = () => setQueryArgument('key', 'value'); return <button onClick={onClick}>Click</button>; }; ``` **After:** ```typescript import { useQueryParamsMutator } from '@console/internal/components/utils/router'; const MyComponent = () => { const { setQueryArgument } = useQueryParamsMutator(); const onClick = () => setQueryArgument('key', 'value'); return <button onClick={onClick}>Click</button>; }; ``` ## Files Migrated (19 of 20) ### Easy Conversions (14 files) - public/components/useRowFilterFix.ts - public/components/useLabelSelectionFix.ts - public/components/useSearchFilters.ts - public/components/filter-toolbar.tsx - public/components/search.tsx - public/components/api-explorer.tsx - public/components/cluster-settings/cluster-settings.tsx - public/components/namespace-bar.tsx - packages/console-app/src/components/nodes/NodeLogs.tsx - packages/console-shared/src/components/catalog/CatalogController.tsx - packages/topology/src/components/page/TopologyPage.tsx - packages/topology/src/components/page/TopologyView.tsx - packages/operator-lifecycle-manager/src/components/subscription.tsx - packages/operator-lifecycle-manager/src/components/operator-hub/operator-channel-version-select.tsx ### Complex Migrations (5 files) **QuickSearch component tree** - Required prop drilling navigate/removeQueryArgument: - packages/console-shared/src/components/quick-search/QuickSearchModalBody.tsx - packages/console-shared/src/components/quick-search/QuickSearchContent.tsx - packages/console-shared/src/components/quick-search/QuickSearchList.tsx - packages/console-shared/src/components/quick-search/QuickSearchDetails.tsx - packages/console-shared/src/components/quick-search/utils/quick-search-utils.tsx **TopologyFilterBar** - Replaced utility functions with direct hook usage: - packages/topology/src/filters/TopologyFilterBar.tsx - packages/topology/src/filters/filter-utils.ts (removed deprecated functions) ## Testing **Unit Tests:** - Added comprehensive tests for new hooks (public/components/utils/__tests__/router-hooks.spec.tsx) - All existing tests pass (20/20 ✓) **Type Safety:** - Zero TypeScript errors - All TSDoc warnings fixed **Code Quality:** - ESLint pre-commit hooks pass - Dependency arrays updated correctly ## Remaining Work - 1 file remains: public/components/pod-logs.jsx (class → functional component conversion) - Manual testing of key user flows - Remove deprecated functions after all migrations complete ## Related - Epic: CONSOLE-4392 (Upgrade to react-router v7) - PR: #15956 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent aa30da3 commit d73bffe

File tree

24 files changed

+1083
-87
lines changed

24 files changed

+1083
-87
lines changed

CONSOLE-4990-PROGRESS.md

Lines changed: 515 additions & 0 deletions
Large diffs are not rendered by default.

frontend/packages/console-app/src/components/nodes/NodeLogs.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@ import { css } from '@patternfly/react-styles';
2222
import { Trans, useTranslation } from 'react-i18next';
2323
import { coFetch } from '@console/internal/co-fetch';
2424
import { ThemeContext } from '@console/internal/components/ThemeProvider';
25-
import {
26-
getQueryArgument,
27-
removeQueryArgument,
28-
setQueryArgument,
29-
} from '@console/internal/components/utils/router';
25+
import { useQueryParamsMutator } from '@console/internal/components/utils/router';
3026
import { LoadingBox, LoadingInline } from '@console/internal/components/utils/status-box';
3127
import { modelFor, NodeKind, resourceURL } from '@console/internal/module/k8s';
3228
import PaneBody from '@console/shared/src/components/layout/PaneBody';
@@ -179,6 +175,8 @@ const HeaderBanner: FC<{ lineCount: number }> = ({ lineCount }) => {
179175
};
180176

181177
const NodeLogs: FC<NodeLogsProps> = ({ obj: node }) => {
178+
const { getQueryArgument, setQueryArgument, removeQueryArgument } = useQueryParamsMutator();
179+
182180
const {
183181
kind,
184182
metadata: { labels, name, namespace: ns },

frontend/packages/console-shared/src/components/catalog/CatalogController.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useLocation } from 'react-router-dom-v5-compat';
66
import { FLAG_TECH_PREVIEW } from '@console/app/src/consts';
77
import { ResolvedExtension, CatalogItemType, CatalogCategory } from '@console/dynamic-plugin-sdk';
88
import { CatalogItem } from '@console/dynamic-plugin-sdk/src/extensions';
9-
import { removeQueryArgument, setQueryArgument } from '@console/internal/components/utils/router';
9+
import { useQueryParamsMutator } from '@console/internal/components/utils/router';
1010
import { skeletonCatalog } from '@console/internal/components/utils/skeleton-catalog';
1111
import { StatusBox } from '@console/internal/components/utils/status-box';
1212
import OLMv1Alert from '@console/operator-lifecycle-manager-v1/src/components/OLMv1Alert';
@@ -51,6 +51,7 @@ const CatalogController: FC<CatalogControllerProps> = ({
5151
hideSidebar,
5252
categories,
5353
}) => {
54+
const { setQueryArgument, removeQueryArgument } = useQueryParamsMutator();
5455
const { t } = useTranslation();
5556
const { pathname } = useLocation();
5657
const queryParams = useQueryParams();
@@ -151,13 +152,16 @@ const CatalogController: FC<CatalogControllerProps> = ({
151152
[catalogItems, filterGroups],
152153
);
153154

154-
const openDetailsPanel = useCallback((item: CatalogItem): void => {
155-
setQueryArgument(CatalogQueryParams.SELECTED_ID, item.uid);
156-
}, []);
155+
const openDetailsPanel = useCallback(
156+
(item: CatalogItem): void => {
157+
setQueryArgument(CatalogQueryParams.SELECTED_ID, item.uid);
158+
},
159+
[setQueryArgument],
160+
);
157161

158162
const closeDetailsPanel = useCallback((): void => {
159163
removeQueryArgument(CatalogQueryParams.SELECTED_ID);
160-
}, []);
164+
}, [removeQueryArgument]);
161165

162166
const renderTile = useCallback(
163167
(item: CatalogItem) => (

frontend/packages/console-shared/src/components/quick-search/QuickSearchContent.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ interface QuickSearchContentProps {
2121
closeModal: () => void;
2222
detailsRenderer?: DetailsRendererFunction;
2323
onListChange?: (items: number) => void;
24+
navigate: (url: string) => void;
25+
removeQueryArgument: (key: string) => void;
2426
}
2527

2628
const QuickSearchContent: FC<QuickSearchContentProps> = ({
@@ -36,6 +38,8 @@ const QuickSearchContent: FC<QuickSearchContentProps> = ({
3638
limitItemCount,
3739
detailsRenderer,
3840
onListChange,
41+
navigate,
42+
removeQueryArgument,
3943
}) => {
4044
return (
4145
<Split className="ocs-quick-search-content" tabIndex={-1}>
@@ -55,6 +59,8 @@ const QuickSearchContent: FC<QuickSearchContentProps> = ({
5559
onSelectListItem={(_event, itemId) => onSelect(itemId)}
5660
closeModal={closeModal}
5761
onListChange={onListChange}
62+
navigate={navigate}
63+
removeQueryArgument={removeQueryArgument}
5864
/>
5965
</SplitItem>
6066
<Divider component="div" orientation={{ default: 'vertical' }} tabIndex={-1} />
@@ -63,6 +69,8 @@ const QuickSearchContent: FC<QuickSearchContentProps> = ({
6369
detailsRenderer={detailsRenderer}
6470
selectedItem={selectedItem}
6571
closeModal={closeModal}
72+
navigate={navigate}
73+
removeQueryArgument={removeQueryArgument}
6674
/>
6775
</SplitItem>
6876
</Split>

frontend/packages/console-shared/src/components/quick-search/QuickSearchDetails.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import './QuickSearchDetails.scss';
1111
export type QuickSearchDetailsRendererProps = {
1212
selectedItem: CatalogItem;
1313
closeModal: () => void;
14+
navigate: (url: string) => void;
15+
removeQueryArgument: (key: string) => void;
1416
};
1517
export type DetailsRendererFunction = (props: QuickSearchDetailsRendererProps) => ReactNode;
1618
export interface QuickSearchDetailsProps extends QuickSearchDetailsRendererProps {
@@ -21,6 +23,8 @@ const QuickSearchDetails: FC<QuickSearchDetailsProps> = ({
2123
selectedItem,
2224
closeModal,
2325
detailsRenderer,
26+
navigate,
27+
removeQueryArgument,
2428
}) => {
2529
const { t } = useTranslation();
2630
const fireTelemetryEvent = useTelemetry();
@@ -46,7 +50,14 @@ const QuickSearchDetails: FC<QuickSearchDetailsProps> = ({
4650
className="ocs-quick-search-details__form-button"
4751
data-test="create-quick-search"
4852
onClick={(e) => {
49-
handleCta(e, props.selectedItem, props.closeModal, fireTelemetryEvent);
53+
handleCta(
54+
e,
55+
props.selectedItem,
56+
props.closeModal,
57+
fireTelemetryEvent,
58+
props.navigate,
59+
props.removeQueryArgument,
60+
);
5061
}}
5162
>
5263
{props.selectedItem.cta.label}
@@ -61,7 +72,7 @@ const QuickSearchDetails: FC<QuickSearchDetailsProps> = ({
6172

6273
return (
6374
<div className="ocs-quick-search-details">
64-
{detailsContentRenderer({ selectedItem, closeModal })}
75+
{detailsContentRenderer({ selectedItem, closeModal, navigate, removeQueryArgument })}
6576
</div>
6677
);
6778
};

frontend/packages/console-shared/src/components/quick-search/QuickSearchList.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ interface QuickSearchListProps {
3535
onSelectListItem: DataListProps['onSelectDataListItem'];
3636
onListChange?: (items: number) => void;
3737
closeModal: () => void;
38+
navigate: (url: string) => void;
39+
removeQueryArgument: (key: string) => void;
3840
}
3941

4042
const QuickSearchList: FC<QuickSearchListProps> = ({
@@ -46,6 +48,8 @@ const QuickSearchList: FC<QuickSearchListProps> = ({
4648
closeModal,
4749
limitItemCount,
4850
onListChange,
51+
navigate,
52+
removeQueryArgument,
4953
}) => {
5054
const { t } = useTranslation();
5155
const fireTelemetryEvent = useTelemetry();
@@ -105,7 +109,7 @@ const QuickSearchList: FC<QuickSearchListProps> = ({
105109
'ocs-quick-search-list__item--highlight': item.uid === selectedItemId,
106110
})}
107111
onDoubleClick={(e: SyntheticEvent) => {
108-
handleCta(e, item, closeModal, fireTelemetryEvent);
112+
handleCta(e, item, closeModal, fireTelemetryEvent, navigate, removeQueryArgument);
109113
}}
110114
>
111115
<DataListItemRow className="ocs-quick-search-list__item-row">

frontend/packages/console-shared/src/components/quick-search/QuickSearchModalBody.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,7 @@ import type { ReactNode, FC, FormEvent } from 'react';
22
import { useState, useRef, useEffect, useCallback } from 'react';
33
import { ModalBody, ModalHeader } from '@patternfly/react-core';
44
import { CatalogItem } from '@console/dynamic-plugin-sdk';
5-
import {
6-
getQueryArgument,
7-
removeQueryArgument,
8-
setQueryArgument,
9-
history,
10-
} from '@console/internal/components/utils/router';
5+
import { useQueryParamsMutator, useRouterPush } from '@console/internal/components/utils/router';
116
import { useTelemetry } from '../../hooks/useTelemetry';
127
import { CatalogType } from '../catalog';
138
import QuickSearchBar from './QuickSearchBar';
@@ -37,6 +32,8 @@ const QuickSearchModalBody: FC<QuickSearchModalBodyProps> = ({
3732
icon,
3833
detailsRenderer,
3934
}) => {
35+
const { getQueryArgument, setQueryArgument, removeQueryArgument } = useQueryParamsMutator();
36+
const navigate = useRouterPush();
4037
const [catalogItems, setCatalogItems] = useState<CatalogItem[]>(null);
4138
const [catalogTypes, setCatalogTypes] = useState<CatalogType[]>([]);
4239
const [searchTerm, setSearchTerm] = useState<string>(getQueryArgument('catalogSearch') || '');
@@ -81,7 +78,7 @@ const QuickSearchModalBody: FC<QuickSearchModalBodyProps> = ({
8178
setSelectedItemId('');
8279
setSelectedItem(null);
8380
},
84-
[searchCatalog],
81+
[searchCatalog, setQueryArgument, removeQueryArgument],
8582
);
8683

8784
const onCancel = useCallback(() => {
@@ -104,12 +101,12 @@ const QuickSearchModalBody: FC<QuickSearchModalBodyProps> = ({
104101
const { id } = document.activeElement;
105102
const activeViewAllLink = viewAll?.find((link) => link.catalogType === id);
106103
if (activeViewAllLink) {
107-
history.push(activeViewAllLink.to);
104+
navigate(activeViewAllLink.to);
108105
} else if (selectedItem) {
109-
handleCta(e, selectedItem, closeModal, fireTelemetryEvent);
106+
handleCta(e, selectedItem, closeModal, fireTelemetryEvent, navigate, removeQueryArgument);
110107
}
111108
},
112-
[closeModal, fireTelemetryEvent, selectedItem, viewAll],
109+
[closeModal, fireTelemetryEvent, selectedItem, viewAll, navigate, removeQueryArgument],
113110
);
114111

115112
const selectPrevious = useCallback(() => {
@@ -198,6 +195,8 @@ const QuickSearchModalBody: FC<QuickSearchModalBodyProps> = ({
198195
limitItemCount={limitItemCount}
199196
detailsRenderer={detailsRenderer}
200197
onListChange={handleListChange}
198+
navigate={navigate}
199+
removeQueryArgument={removeQueryArgument}
201200
onSelect={(itemId) => {
202201
setSelectedItemId(itemId);
203202
setSelectedItem(catalogItems?.find((item) => item.uid === itemId));

frontend/packages/console-shared/src/components/quick-search/utils/quick-search-utils.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { SyntheticEvent } from 'react';
22
import { CatalogItem } from '@console/dynamic-plugin-sdk';
3-
import { history, removeQueryArgument } from '@console/internal/components/utils/router';
43
import { keywordCompare } from '../../catalog';
54

65
export const quickSearch = (items: CatalogItem[], query: string) => {
@@ -12,6 +11,8 @@ export const handleCta = async (
1211
item: CatalogItem,
1312
closeModal: () => void,
1413
fireTelemetryEvent: (event: string, properties?: {}) => void,
14+
navigate: (url: string) => void,
15+
removeQueryArg: (key: string) => void,
1516
callbackProps: { [key: string]: string } = {},
1617
) => {
1718
e.preventDefault();
@@ -24,6 +25,6 @@ export const handleCta = async (
2425
});
2526
closeModal();
2627
await callback(callbackProps);
27-
removeQueryArgument('catalogSearch');
28-
} else history.push(href);
28+
removeQueryArg('catalogSearch');
29+
} else navigate(href);
2930
};

frontend/packages/operator-lifecycle-manager/src/components/operator-hub/operator-channel-version-select.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '@patternfly/react-core';
1111
import * as _ from 'lodash';
1212
import { useTranslation } from 'react-i18next';
13-
import { setQueryArgument } from '@console/internal/components/utils';
13+
import { useQueryParamsMutator } from '@console/internal/components/utils/router';
1414
import { alphanumericCompare } from '@console/shared';
1515
import { PackageManifestKind } from '../../types';
1616
import { DeprecatedOperatorWarningIcon } from '../deprecated-operator-warnings/deprecated-operator-warnings';
@@ -23,6 +23,7 @@ export const OperatorChannelSelect: FC<OperatorChannelSelectProps> = ({
2323
setUpdateVersion,
2424
}) => {
2525
const { t } = useTranslation();
26+
const { setQueryArgument } = useQueryParamsMutator();
2627
const channels = useMemo(() => packageManifest?.status.channels ?? [], [packageManifest]);
2728
const [isChannelSelectOpen, setIsChannelSelectOpen] = useState(false);
2829
const { setDeprecatedChannel } = useDeprecatedOperatorWarnings();
@@ -63,7 +64,7 @@ export const OperatorChannelSelect: FC<OperatorChannelSelectProps> = ({
6364
'deprecation',
6465
),
6566
);
66-
}, [selectedChannel, channels, setDeprecatedChannel]);
67+
}, [selectedChannel, channels, setDeprecatedChannel, setQueryArgument]);
6768

6869
return (
6970
<>
@@ -113,6 +114,7 @@ export const OperatorVersionSelect: FC<OperatorVersionSelectProps> = ({
113114
showVersionAlert = false,
114115
}) => {
115116
const { t } = useTranslation();
117+
const { setQueryArgument } = useQueryParamsMutator();
116118
const { setDeprecatedVersion } = useDeprecatedOperatorWarnings();
117119
const [isVersionSelectOpen, setIsVersionSelectOpen] = useState(false);
118120
const [defaultVersionForChannel, setDefaultVersionForChannel] = useState('-');
@@ -163,7 +165,7 @@ export const OperatorVersionSelect: FC<OperatorVersionSelectProps> = ({
163165
'deprecation',
164166
),
165167
);
166-
}, [selectedUpdateVersion, selectedChannelVersions, setDeprecatedVersion]);
168+
}, [selectedUpdateVersion, selectedChannelVersions, setDeprecatedVersion, setQueryArgument]);
167169

168170
return (
169171
<>

frontend/packages/operator-lifecycle-manager/src/components/subscription.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
ResourceSummary,
4545
SectionHeading,
4646
} from '@console/internal/components/utils';
47-
import { removeQueryArgument } from '@console/internal/components/utils/router';
47+
import { useQueryParamsMutator } from '@console/internal/components/utils/router';
4848
import {
4949
k8sGet,
5050
k8sKill,
@@ -419,6 +419,7 @@ export const SubscriptionDetails: FC<SubscriptionDetailsProps> = ({
419419
subscriptions = [],
420420
}) => {
421421
const { t } = useTranslation();
422+
const { removeQueryArgument } = useQueryParamsMutator();
422423
const { source, sourceNamespace } = obj?.spec ?? {};
423424
const catalogHealth = obj?.status?.catalogHealth?.find(
424425
(ch) => ch.catalogSourceRef.name === source,

0 commit comments

Comments
 (0)