diff --git a/package.json b/package.json index d501a6aa9..2a8f21f23 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "prettier": "^3.0.3", "prop-types": "^15.7.2", "raw-loader": "^4.0.2", - "react-router-dom": "^5.3.4", "react-test-renderer": "^17.0.2", "regenerator-runtime": "^0.13.11", "rimraf": "^4.1.2", @@ -87,8 +86,7 @@ "webpack-bundle-analyzer": "^4.8.0", "webpack-cli": "^5.0.0", "webpack-dev-server": "^4.15.1", - "webpack-merge": "^5.8.0", - "yarn": "^1.22.19" + "webpack-merge": "^5.8.0" }, "dependencies": { "@patternfly/quickstarts": "^2.3.3", @@ -101,7 +99,7 @@ "@patternfly/react-topology": "4.91.27", "@reduxjs/toolkit": "^1.9.3", "@types/lodash": "^4.14.191", - "@types/react-router-dom": "^5.3.3", + "@types/react": "^17.0.69", "dayjs": "^1.11.7", "i18next": "^22.4.10", "i18next-browser-languagedetector": "^7.0.1", @@ -114,9 +112,13 @@ "react-i18next": "^12.3.1", "react-joyride": "^2.5.3", "react-redux": "^8.0.5", + "react-router-dom": "^6.17.0", "rxjs": "^7.8.0", "semver": "^7.5.4", "showdown": "^2.1.0" }, + "resolutions": { + "@types/react": "^17.0.69" + }, "packageManager": "yarn@3.6.3" } diff --git a/src/app/AppLayout/AppLayout.tsx b/src/app/AppLayout/AppLayout.tsx index add47198d..4fdda4976 100644 --- a/src/app/AppLayout/AppLayout.tsx +++ b/src/app/AppLayout/AppLayout.tsx @@ -81,7 +81,7 @@ import { import _ from 'lodash'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Link, matchPath, NavLink, useHistory, useLocation } from 'react-router-dom'; +import { Link, matchPath, NavLink, useLocation, useNavigate } from 'react-router-dom'; import { map } from 'rxjs/operators'; export interface AppLayoutProps { @@ -92,7 +92,6 @@ export const AppLayout: React.FC = ({ children }) => { const serviceContext = React.useContext(ServiceContext); const notificationsContext = React.useContext(NotificationsContext); const addSubscription = useSubscriptions(); - const routerHistory = useHistory(); const { t } = useTranslation(); const { setState: setJoyState, @@ -117,6 +116,7 @@ export const AppLayout: React.FC = ({ children }) => { const [errorNotificationsCount, setErrorNotificationsCount] = React.useState(0); const [activeLevel, setActiveLevel] = React.useState(FeatureLevel.PRODUCTION); const location = useLocation(); + const navigate = useNavigate(); const [theme] = useTheme(); React.useEffect(() => { @@ -251,10 +251,6 @@ export const AppLayout: React.FC = ({ children }) => { [isMobileView, setIsNavOpen], ); - const handleSettingsButtonClick = React.useCallback(() => { - routerHistory.push('/settings'); - }, [routerHistory]); - const handleNotificationCenterToggle = React.useCallback(() => { notificationsContext.setDrawerState(!isNotificationDrawerExpanded); }, [isNotificationDrawerExpanded, notificationsContext]); @@ -272,13 +268,12 @@ export const AppLayout: React.FC = ({ children }) => { }, [serviceContext.login, addSubscription]); const handleLanguagePref = React.useCallback(() => { - if (routerHistory.location.pathname === '/settings') { + if (location.pathname === '/settings') { selectTab(SettingTab.GENERAL); } else { - const query = new URLSearchParams({ tab: tabAsParam(SettingTab.GENERAL) }); - routerHistory.push(`/settings?${query}`); + navigate(`/settings?${new URLSearchParams({ tab: tabAsParam(SettingTab.GENERAL) })}`); } - }, [routerHistory]); + }, [location, navigate]); const handleUserInfoToggle = React.useCallback(() => setShowUserInfoDropdown((v) => !v), [setShowUserInfoDropdown]); @@ -400,13 +395,14 @@ export const AppLayout: React.FC = ({ children }) => { = ({ children }) => { unreadNotificationsCount, errorNotificationsCount, handleNotificationCenterToggle, - handleSettingsButtonClick, handleHelpToggle, setShowUserInfoDropdown, showUserIcon, @@ -489,7 +484,7 @@ export const AppLayout: React.FC = ({ children }) => { const isActiveRoute = React.useCallback( (route: IAppRoute): boolean => { const match = matchPath(location.pathname, route.path); - if (match && match.isExact) { + if (match) { return true; } else if (route.children) { let childMatch = false; @@ -521,9 +516,9 @@ export const AppLayout: React.FC = ({ children }) => { isActive={isActiveRoute(route)} > (active ? 'pf-m-current' : undefined)} data-quickstart-id={`nav-${cleanDataId(route.label)}-tab`} data-tour-id={`${cleanDataId(route.label)}`} > diff --git a/src/app/AppLayout/SslErrorModal.tsx b/src/app/AppLayout/SslErrorModal.tsx index 49a0d7f5d..4ea2ee04d 100644 --- a/src/app/AppLayout/SslErrorModal.tsx +++ b/src/app/AppLayout/SslErrorModal.tsx @@ -16,7 +16,7 @@ import { portalRoot } from '@app/utils/utils'; import { Button, Modal, ModalVariant, Text } from '@patternfly/react-core'; import * as React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; export interface SslErrorModalProps { visible: boolean; @@ -24,12 +24,12 @@ export interface SslErrorModalProps { } export const SslErrorModal: React.FC = ({ visible, onDismiss }) => { - const routerHistory = useHistory(); + const navigate = useNavigate(); - const handleClick = () => { - routerHistory.push('/security'); + const handleClick = React.useCallback(() => { + navigate('/security'); onDismiss(); - }; + }, [navigate, onDismiss]); return ( = ({ ...props }) => { const { search, pathname } = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const context = React.useContext(ServiceContext); const addSubscription = useSubscriptions(); @@ -64,8 +64,8 @@ export const Archives: React.FC = ({ ...props }) => { const onTabSelect = React.useCallback( (_: React.MouseEvent, key: string | number) => - switchTab(history, pathname, search, { tabKey: 'tab', tabValue: `${key}` }), - [history, pathname, search], + switchTab(navigate, pathname, search, { tabKey: 'tab', tabValue: `${key}` }), + [navigate, pathname, search], ); const uploadTargetAsObs = React.useMemo(() => of(uploadAsTarget), []); diff --git a/src/app/CreateRecording/CustomRecordingForm.tsx b/src/app/CreateRecording/CustomRecordingForm.tsx index ee83c7a5b..6b93cbc65 100644 --- a/src/app/CreateRecording/CustomRecordingForm.tsx +++ b/src/app/CreateRecording/CustomRecordingForm.tsx @@ -48,7 +48,7 @@ import { } from '@patternfly/react-core'; import { HelpIcon } from '@patternfly/react-icons'; import * as React from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { forkJoin } from 'rxjs'; import { first } from 'rxjs/operators'; import { EventTemplateIdentifier, CustomRecordingFormData } from './types'; @@ -57,9 +57,9 @@ import { isDurationValid, isRecordingNameValid } from './utils'; export const CustomRecordingForm: React.FC = () => { const context = React.useContext(ServiceContext); const notifications = React.useContext(NotificationsContext); - const history = useHistory(); + const navigate = useNavigate(); const addSubscription = useSubscriptions(); - const location = useLocation | undefined>(); + const location = useLocation(); const [formData, setFormData] = React.useState({ name: '', @@ -82,7 +82,7 @@ export const CustomRecordingForm: React.FC = () => { const [loading, setLoading] = React.useState(false); const [errorMessage, setErrorMessage] = React.useState(''); - const exitForm = React.useCallback(() => history.goBack(), [history]); + const exitForm = React.useCallback(() => navigate('..', { relative: 'path' }), [navigate]); const handleCreateRecording = React.useCallback( (recordingAttributes: RecordingAttributes) => { @@ -357,6 +357,7 @@ export const CustomRecordingForm: React.FC = () => { }, [addSubscription, context.target, refreshFormOptions]); React.useEffect(() => { + const prefilled: Partial = location.state || {}; const { name, restart, @@ -370,7 +371,7 @@ export const CustomRecordingForm: React.FC = () => { maxSizeUnit, continuous, skipDurationCheck, - } = location.state || {}; + } = prefilled; setFormData((old) => ({ ...old, name: name ?? '', @@ -631,7 +632,7 @@ export const CustomRecordingForm: React.FC = () => { > {loading ? 'Creating' : 'Create'} - diff --git a/src/app/CreateRecording/SnapshotRecordingForm.tsx b/src/app/CreateRecording/SnapshotRecordingForm.tsx index 3fcfed52d..7499d31e7 100644 --- a/src/app/CreateRecording/SnapshotRecordingForm.tsx +++ b/src/app/CreateRecording/SnapshotRecordingForm.tsx @@ -20,18 +20,20 @@ import { ServiceContext } from '@app/Shared/Services/Services'; import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { ActionGroup, Button, Form, Text, TextVariants } from '@patternfly/react-core'; import * as React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { first } from 'rxjs'; export interface SnapshotRecordingFormProps {} export const SnapshotRecordingForm: React.FC = (_) => { - const history = useHistory(); + const navigate = useNavigate(); const addSubscription = useSubscriptions(); const context = React.useContext(ServiceContext); const [loading, setLoading] = React.useState(false); const [errorMessage, setErrorMessage] = React.useState(''); + const exitForm = React.useCallback(() => navigate('..', { relative: 'path' }), [navigate]); + const handleCreateSnapshot = React.useCallback(() => { setLoading(true); addSubscription( @@ -41,11 +43,11 @@ export const SnapshotRecordingForm: React.FC = (_) = .subscribe((success) => { setLoading(false); if (success) { - history.push('/recordings'); + exitForm(); } }), ); - }, [addSubscription, context.api, history, setLoading]); + }, [addSubscription, context.api, exitForm, setLoading]); const createButtonLoadingProps = React.useMemo( () => @@ -117,7 +119,7 @@ export const SnapshotRecordingForm: React.FC = (_) = - diff --git a/src/app/Dashboard/Charts/jfr/JFRMetricsChartCard.tsx b/src/app/Dashboard/Charts/jfr/JFRMetricsChartCard.tsx index 7dba0895d..37f644857 100644 --- a/src/app/Dashboard/Charts/jfr/JFRMetricsChartCard.tsx +++ b/src/app/Dashboard/Charts/jfr/JFRMetricsChartCard.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { CustomRecordingFormData } from '@app/CreateRecording/types'; import { DashboardCardTypeProps, DashboardCardFC, @@ -43,7 +42,7 @@ import { import { DataSourceIcon, ExternalLinkAltIcon, SyncAltIcon, TachometerAltIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { interval } from 'rxjs'; import { DashboardCard } from '../../DashboardCard'; import { ChartContext } from '../context'; @@ -91,7 +90,7 @@ export const JFRMetricsChartCard: DashboardCardFC = (p const [t] = useTranslation(); const serviceContext = React.useContext(ServiceContext); const controllerContext = React.useContext(ChartContext); - const history = useHistory(); + const navigate = useNavigate(); const addSubscription = useSubscriptions(); const [theme] = useTheme(); const [controllerState, setControllerState] = React.useState(ControllerState.NO_DATA); @@ -204,8 +203,7 @@ export const JFRMetricsChartCard: DashboardCardFC = (p }, [props.actions, props.chartKind, props.duration, props.period, t, controllerState, actions]); const handleCreateRecording = React.useCallback(() => { - history.push({ - pathname: '/recordings/create', + navigate('/recordings/create', { state: { name: RECORDING_NAME, template: { @@ -222,9 +220,9 @@ export const JFRMetricsChartCard: DashboardCardFC = (p maxAgeUnit: 1, // seconds maxSize: 100 * 1024 * 1024, maxSizeUnit: 1, // bytes - } as Partial, + }, }); - }, [history]); + }, [navigate]); return ( = (_) => { - const history = useHistory(); + const navigate = useNavigate(); const serviceContext = React.useContext(ServiceContext); const dispatch = useDispatch(); const { t } = useTranslation(); @@ -130,7 +130,7 @@ export const Dashboard: React.FC = (_) => { key={`${cfg.name}-actions`} onRemove={() => handleRemove(idx)} onResetSize={() => handleResetSize(idx)} - onView={() => history.push(`/d-solo?layout=${currLayout.name}&cardId=${cfg.id}`)} + onView={() => navigate(`/d-solo?layout=${currLayout.name}&cardId=${cfg.id}`)} />, ], }) diff --git a/src/app/Dashboard/DashboardSolo.tsx b/src/app/Dashboard/DashboardSolo.tsx index 6b0297af1..194765686 100644 --- a/src/app/Dashboard/DashboardSolo.tsx +++ b/src/app/Dashboard/DashboardSolo.tsx @@ -19,7 +19,7 @@ import { Bullseye, Button, EmptyState, EmptyStateBody, EmptyStateIcon, Title } f import { MonitoringIcon } from '@patternfly/react-icons'; import * as React from 'react'; import { useSelector } from 'react-redux'; -import { useHistory, useLocation, withRouter } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { CardConfig } from './types'; import { getCardDescriptorByName } from './utils'; @@ -27,7 +27,7 @@ export interface DashboardSoloProps {} const DashboardSolo: React.FC = () => { const { search } = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const dashboardConfigs = useSelector((state: RootState) => state.dashboardConfigs); @@ -59,7 +59,7 @@ const DashboardSolo: React.FC = () => { Provide valid layout and cardId query parameters and try again. - @@ -84,9 +84,9 @@ const DashboardSolo: React.FC = () => { ); } - }, [cardConfig, history]); + }, [cardConfig, navigate]); return content; }; -export default withRouter(DashboardSolo); +export default DashboardSolo; diff --git a/src/app/Events/EventTemplates.tsx b/src/app/Events/EventTemplates.tsx index 97a062272..ddb559afe 100644 --- a/src/app/Events/EventTemplates.tsx +++ b/src/app/Events/EventTemplates.tsx @@ -58,7 +58,7 @@ import { Tr, } from '@patternfly/react-table'; import * as React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { forkJoin, Observable, of } from 'rxjs'; import { catchError, concatMap, defaultIfEmpty, filter, first, tap } from 'rxjs/operators'; @@ -89,7 +89,7 @@ export interface EventTemplatesProps {} export const EventTemplates: React.FC = (_) => { const context = React.useContext(ServiceContext); - const history = useHistory(); + const navigate = useNavigate(); const [templates, setTemplates] = React.useState([]); const [filteredTemplates, setFilteredTemplates] = React.useState([]); @@ -256,8 +256,7 @@ export const EventTemplates: React.FC = (_) => { { title: 'Create Recording...', onClick: () => - history.push({ - pathname: '/recordings/create', + navigate('/recordings/create', { state: { template: { name: t.name, type: t.type } } as Partial, }), }, @@ -284,7 +283,7 @@ export const EventTemplates: React.FC = (_) => { } return actions; }, - [context.api, history, handleDeleteButton], + [context.api, navigate, handleDeleteButton], ); const handleUploadModalClose = React.useCallback(() => { diff --git a/src/app/Events/Events.tsx b/src/app/Events/Events.tsx index 033111242..167a02a62 100644 --- a/src/app/Events/Events.tsx +++ b/src/app/Events/Events.tsx @@ -21,7 +21,7 @@ import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { getActiveTab, switchTab } from '@app/utils/utils'; import { Card, CardBody, Stack, StackItem, Tab, Tabs, Tooltip } from '@patternfly/react-core'; import * as React from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { concatMap, filter } from 'rxjs'; import { EventTemplates } from './EventTemplates'; import { EventTypes } from './EventTypes'; @@ -58,7 +58,7 @@ enum EventTab { export const EventTabs: React.FC = () => { const { search, pathname } = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const activeTab = React.useMemo(() => { return getActiveTab(search, 'eventTab', Object.values(EventTab), EventTab.EVENT_TEMPLATE); @@ -66,8 +66,8 @@ export const EventTabs: React.FC = () => { const onTabSelect = React.useCallback( (_: React.MouseEvent, key: string | number) => - switchTab(history, pathname, search, { tabKey: 'eventTab', tabValue: `${key}` }), - [history, pathname, search], + switchTab(navigate, pathname, search, { tabKey: 'eventTab', tabValue: `${key}` }), + [navigate, pathname, search], ); return ( @@ -92,7 +92,7 @@ export const AgentTabs: React.FC = () => { const addSubscription = useSubscriptions(); const { search, pathname } = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const activeTab = React.useMemo(() => { return getActiveTab(search, 'agentTab', Object.values(AgentTab), AgentTab.AGENT_TEMPLATE); @@ -102,8 +102,8 @@ export const AgentTabs: React.FC = () => { const onTabSelect = React.useCallback( (_: React.MouseEvent, key: string | number) => - switchTab(history, pathname, search, { tabKey: 'agentTab', tabValue: `${key}` }), - [history, pathname, search], + switchTab(navigate, pathname, search, { tabKey: 'agentTab', tabValue: `${key}` }), + [navigate, pathname, search], ); React.useEffect(() => { diff --git a/src/app/QuickStarts/QuickStartsCatalogPage.tsx b/src/app/QuickStarts/QuickStartsCatalogPage.tsx index 48275d849..7793c56db 100644 --- a/src/app/QuickStarts/QuickStartsCatalogPage.tsx +++ b/src/app/QuickStarts/QuickStartsCatalogPage.tsx @@ -16,7 +16,6 @@ import { QuickStartCatalogPage } from '@patternfly/quickstarts'; import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { withRouter } from 'react-router-dom'; export interface QuickStartsCatalogPageProps {} @@ -36,4 +35,4 @@ const QuickStartsCatalogPage: React.FC = (_) => { ); }; -export default withRouter(QuickStartsCatalogPage); +export default QuickStartsCatalogPage; diff --git a/src/app/Recordings/ActiveRecordingsTable.tsx b/src/app/Recordings/ActiveRecordingsTable.tsx index b1d4c861c..d67a1f287 100644 --- a/src/app/Recordings/ActiveRecordingsTable.tsx +++ b/src/app/Recordings/ActiveRecordingsTable.tsx @@ -78,7 +78,7 @@ import { RedoIcon } from '@patternfly/react-icons'; import { ExpandableRowContent, SortByDirection, Tbody, Td, Tr } from '@patternfly/react-table'; import * as React from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory, useRouteMatch } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { combineLatest, forkJoin, merge, Observable } from 'rxjs'; import { concatMap, filter, first } from 'rxjs/operators'; import { DeleteWarningModal } from '../Modal/DeleteWarningModal'; @@ -136,9 +136,7 @@ export interface ActiveRecordingsTableProps { export const ActiveRecordingsTable: React.FC = (props) => { const context = React.useContext(ServiceContext); - - const routerHistory = useHistory(); - const { url } = useRouteMatch(); + const navigate = useNavigate(); const addSubscription = useSubscriptions(); const dispatch = useDispatch(); @@ -187,8 +185,8 @@ export const ActiveRecordingsTable: React.FC = (prop ); const handleCreateRecording = React.useCallback(() => { - routerHistory.push(`${url}/create`); - }, [routerHistory, url]); + navigate('create', { relative: 'path' }); + }, [navigate]); const handleEditLabels = React.useCallback(() => { setShowDetailsPanel(true); diff --git a/src/app/Recordings/Recordings.tsx b/src/app/Recordings/Recordings.tsx index a6bbe1b7a..f72c3c1fb 100644 --- a/src/app/Recordings/Recordings.tsx +++ b/src/app/Recordings/Recordings.tsx @@ -19,7 +19,7 @@ import { useSubscriptions } from '@app/utils/hooks/useSubscriptions'; import { getActiveTab, switchTab } from '@app/utils/utils'; import { Card, CardBody, CardTitle, Tab, Tabs, TabTitleText } from '@patternfly/react-core'; import * as React from 'react'; -import { useHistory, useLocation } from 'react-router'; +import { useLocation, useNavigate } from 'react-router'; import { ActiveRecordingsTable } from './ActiveRecordingsTable'; import { ArchivedRecordingsTable } from './ArchivedRecordingsTable'; @@ -32,7 +32,7 @@ export interface RecordingsProps {} export const Recordings: React.FC = ({ ...props }) => { const { search, pathname } = useLocation(); - const history = useHistory(); + const navigate = useNavigate(); const context = React.useContext(ServiceContext); const addSubscription = useSubscriptions(); @@ -48,8 +48,8 @@ export const Recordings: React.FC = ({ ...props }) => { const onTabSelect = React.useCallback( (_: React.MouseEvent, key: string | number) => - switchTab(history, pathname, search, { tabKey: 'tab', tabValue: `${key}` }), - [history, pathname, search], + switchTab(navigate, pathname, search, { tabKey: 'tab', tabValue: `${key}` }), + [navigate, pathname, search], ); const targetAsObs = React.useMemo(() => context.target.target(), [context.target]); diff --git a/src/app/Rules/CreateRule.tsx b/src/app/Rules/CreateRule.tsx index 0033df2ac..6d770cae3 100644 --- a/src/app/Rules/CreateRule.tsx +++ b/src/app/Rules/CreateRule.tsx @@ -53,7 +53,7 @@ import { import { HelpIcon } from '@patternfly/react-icons'; import _ from 'lodash'; import * as React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { combineLatest, forkJoin, iif, of, Subject } from 'rxjs'; import { catchError, debounceTime, map, switchMap, tap } from 'rxjs/operators'; import { RuleFormData } from './types'; @@ -64,7 +64,7 @@ export interface CreateRuleFormProps {} export const CreateRuleForm: React.FC = (_props) => { const context = React.useContext(ServiceContext); const notifications = React.useContext(NotificationsContext); - const history = useHistory(); + const navigate = useNavigate(); // Do not use useSearchExpression for display. This causes the cursor to jump to the end due to async updates. const matchExprService = useMatchExpressionSvc(); const addSubscription = useSubscriptions(); @@ -205,7 +205,7 @@ export const CreateRuleForm: React.FC = (_props) => { [setFormData], ); - const exitForm = React.useCallback(() => history.push('/rules'), [history]); + const exitForm = React.useCallback(() => navigate('..', { relative: 'path' }), [navigate]); const handleSubmit = React.useCallback((): void => { const notificationMessages: string[] = []; @@ -637,7 +637,7 @@ enabled in the future.`} > {loading ? 'Creating' : 'Create'} - diff --git a/src/app/Rules/Rules.tsx b/src/app/Rules/Rules.tsx index f22f5203f..1f30f0417 100644 --- a/src/app/Rules/Rules.tsx +++ b/src/app/Rules/Rules.tsx @@ -51,7 +51,7 @@ import { Tr, } from '@patternfly/react-table'; import * as React from 'react'; -import { Link, useHistory, useRouteMatch } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { first } from 'rxjs/operators'; import { RuleDeleteWarningModal } from './RuleDeleteWarningModal'; import { RuleUploadModal } from './RulesUploadModal'; @@ -118,10 +118,9 @@ export interface RulesTableProps {} export const RulesTable: React.FC = (_) => { const context = React.useContext(ServiceContext); - const routerHistory = useHistory(); + const navigate = useNavigate(); const addSubscription = useSubscriptions(); - const { url } = useRouteMatch(); const [isLoading, setIsLoading] = React.useState(false); const [sortBy, setSortBy] = React.useState({} as ISortBy); const [rules, setRules] = React.useState([] as Rule[]); @@ -202,8 +201,8 @@ export const RulesTable: React.FC = (_) => { }, [context.settings, refreshRules]); const handleCreateRule = React.useCallback(() => { - routerHistory.push(`${url}/create`); - }, [routerHistory, url]); + navigate('create', { relative: 'path' }); + }, [navigate]); const handleUploadRule = React.useCallback(() => { setIsUploadModalOpen(true); diff --git a/src/app/Settings/Settings.tsx b/src/app/Settings/Settings.tsx index 92f75f91e..93926538d 100644 --- a/src/app/Settings/Settings.tsx +++ b/src/app/Settings/Settings.tsx @@ -37,7 +37,7 @@ import { } from '@patternfly/react-core'; import * as React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import { AutomatedAnalysis } from './Config/AutomatedAnalysis'; import { AutoRefresh } from './Config/AutoRefresh'; import { ChartCards } from './Config/ChartCards'; @@ -96,7 +96,7 @@ export const Settings: React.FC = (_) => { [t, loggedIn], ); - const history = useHistory(); + const navigate = useNavigate(); const { search, pathname } = useLocation(); const activeTab = React.useMemo(() => { @@ -112,8 +112,8 @@ export const Settings: React.FC = (_) => { const onTabSelect = React.useCallback( (_: React.MouseEvent, key: string | number) => - switchTab(history, pathname, search, { tabKey: 'tab', tabValue: `${tabAsParam(key as SettingTab)}` }), - [history, pathname, search], + switchTab(navigate, pathname, search, { tabKey: 'tab', tabValue: `${tabAsParam(key as SettingTab)}` }), + [navigate, pathname, search], ); const settingGroups = React.useMemo(() => { diff --git a/src/app/Shared/Components/FileUploads.tsx b/src/app/Shared/Components/FileUploads.tsx index 1e782d347..61203e23a 100644 --- a/src/app/Shared/Components/FileUploads.tsx +++ b/src/app/Shared/Components/FileUploads.tsx @@ -24,7 +24,6 @@ import { } from '@patternfly/react-core'; import { InProgressIcon, UploadIcon } from '@patternfly/react-icons'; import * as React from 'react'; -import { Prompt } from 'react-router-dom'; import { Subject } from 'rxjs'; export type ProgressVariant = 'success' | 'danger' | 'warning'; @@ -258,7 +257,11 @@ export const MultiFileUpload: React.FC = ({ return ( <> - + {/* + TODO: Add back when supported + Reference: https://reactrouter.com/en/main/upgrading/v5#prompt-is-not-currently-supported + + */} connectUrl && !connectUrl.match(/\s+/); @@ -74,7 +74,7 @@ export interface CreateTargetProps { export const CreateTarget: React.FC = ({ prefilled }) => { const addSubscription = useSubscriptions(); const context = React.useContext(ServiceContext); - const history = useHistory(); + const navigate = useNavigate(); const [t] = useTranslation(); const [example, setExample] = React.useState(''); @@ -166,6 +166,8 @@ export const CreateTarget: React.FC = ({ prefilled }) => { [setFormData, resetTestState], ); + const exitForm = React.useCallback(() => navigate('..', { relative: 'path' }), [navigate]); + const handleSubmit = React.useCallback(() => { setLoading(true); // Get storage location @@ -184,7 +186,7 @@ export const CreateTarget: React.FC = ({ prefilled }) => { setLoading(false); const option = isHttpOk(status) ? ValidatedOptions.success : ValidatedOptions.error; if (option === ValidatedOptions.success) { - history.push('/topology'); + exitForm(); } else { setValidation({ option: option, @@ -193,7 +195,7 @@ export const CreateTarget: React.FC = ({ prefilled }) => { } }), ); - }, [setLoading, setValidation, addSubscription, context.api, connectUrl, alias, history, credentials]); + }, [setLoading, setValidation, addSubscription, context.api, connectUrl, alias, exitForm, credentials]); const testTarget = React.useCallback(() => { if (!isValidTargetConnectURL(connectUrl)) { @@ -223,8 +225,6 @@ export const CreateTarget: React.FC = ({ prefilled }) => { resetTestState(); }, [connectUrl, alias, credentials, addSubscription, context.api, resetTestState, setTesting]); - const handleCancel = React.useCallback(() => history.goBack(), [history]); - React.useEffect(() => { if (prefilled) { const { connectUrl, alias, username, password } = prefilled; @@ -413,7 +413,7 @@ export const CreateTarget: React.FC = ({ prefilled }) => { > {loading ? t('CREATING', { ns: 'common' }) : t('CREATE', { ns: 'common' })} - diff --git a/src/app/Topology/Actions/NodeActions.tsx b/src/app/Topology/Actions/NodeActions.tsx index 86acb0d31..a597f8a5d 100644 --- a/src/app/Topology/Actions/NodeActions.tsx +++ b/src/app/Topology/Actions/NodeActions.tsx @@ -21,7 +21,7 @@ import { Dropdown, DropdownItem, DropdownProps, DropdownToggle } from '@patternf import { css } from '@patternfly/react-styles'; import { ContextMenuItem as PFContextMenuItem } from '@patternfly/react-topology'; import * as React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { Observable, Subject, switchMap } from 'rxjs'; import { GraphElement, ListElement } from '../Shared/types'; import { ActionUtils, MenuItemComponent, MenuItemVariant, NodeActionFunction } from './types'; @@ -45,7 +45,7 @@ export const ContextMenuItem: React.FC = ({ isDisabled, ...props }) => { - const history = useHistory(); + const navigate = useNavigate(); const addSubscription = useSubscriptions(); const services = React.useContext(ServiceContext); const notifications = React.useContext(NotificationsContext); @@ -57,9 +57,9 @@ export const ContextMenuItem: React.FC = ({ const handleOnclick = React.useCallback( (e: MouseEvent) => { e.stopPropagation(); - onClick && onClick(element, { history, services, notifications }); + onClick && onClick(element, { navigate, services, notifications }); }, - [onClick, history, services, notifications, element], + [onClick, navigate, services, notifications, element], ); React.useEffect(() => { @@ -69,13 +69,13 @@ export const ContextMenuItem: React.FC = ({ .pipe( switchMap((element) => { setDisabled(true); - return isDisabled(element, { services, notifications, history }); + return isDisabled(element, { services, notifications, navigate }); }), ) .subscribe(setDisabled), ); } - }, [addSubscription, elementSubj, isDisabled, setDisabled, services, notifications, history]); + }, [addSubscription, elementSubj, isDisabled, setDisabled, services, notifications, navigate]); React.useEffect(() => { elementSubj.next(element); diff --git a/src/app/Topology/Actions/QuickSearchPanel.tsx b/src/app/Topology/Actions/QuickSearchPanel.tsx index 35d716be8..f0aca0c2e 100644 --- a/src/app/Topology/Actions/QuickSearchPanel.tsx +++ b/src/app/Topology/Actions/QuickSearchPanel.tsx @@ -48,19 +48,19 @@ import { SearchIcon } from '@patternfly/react-icons'; import { css } from '@patternfly/react-styles'; import { useHover } from '@patternfly/react-topology'; import * as React from 'react'; -import { Link, useHistory } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import QuickSearchIcon from '../../Shared/Components/QuickSearchIcon'; import quickSearches, { QuickSearchId, quickSearchIds } from './quicksearches/all-quick-searches'; import { QuickSearchItem } from './types'; export const QuickSearchTabContent: React.FC<{ item?: QuickSearchItem }> = ({ item, ...props }) => { - const history = useHistory(); + const navigate = useNavigate(); const services = React.useContext(ServiceContext); const notifications = React.useContext(NotificationsContext); const handleActionClick = React.useCallback(() => { - item?.createAction && item.createAction({ history, services, notifications }); - }, [item, history, services, notifications]); + item?.createAction && item.createAction({ navigate, services, notifications }); + }, [item, navigate, services, notifications]); return item ? ( @@ -285,7 +285,7 @@ export interface QuickSearchFlyoutMenuProps { } export const QuickSearchFlyoutMenu: React.FC = ({ isShow, ...props }) => { - const history = useHistory(); + const navigate = useNavigate(); const services = React.useContext(ServiceContext); const notifications = React.useContext(NotificationsContext); const activeLevel = useFeatureLevel(); @@ -307,12 +307,12 @@ export const QuickSearchFlyoutMenu: React.FC = ({ is {icon} } - onClick={() => createAction({ history, services, notifications })} + onClick={() => createAction({ navigate, services, notifications })} > {name} )); - }, [filteredQuicksearches, history, services, notifications]); + }, [filteredQuicksearches, navigate, services, notifications]); return isShow || hover ? ( {} +export interface WarningResolverAsLinkProps extends LinkProps {} export const WarningResolverAsLink: React.FC = ({ to, children, ...props }) => { return ( @@ -43,13 +43,13 @@ export const WarningResolverAsActionButton: React.FC { - const history = useHistory(); + const navigate = useNavigate(); const services = React.useContext(ServiceContext); const notifications = React.useContext(NotificationsContext); const handleClick = React.useCallback(() => { - onClick && onClick(targetNode, { history, services, notifications }); - }, [onClick, targetNode, history, services, notifications]); + onClick && onClick(targetNode, { navigate, services, notifications }); + }, [onClick, targetNode, navigate, services, notifications]); return (