Skip to content

Commit a8df6a2

Browse files
committed
React compatibility improvement
This makes the UI work across OCP 4.19 to 4.22. 4.22 uses react v6 so a small compatility layer is needed. Tested manually on 4.18, 4.19, 4.20, 4.21 and 4.22
1 parent 72199d8 commit a8df6a2

6 files changed

Lines changed: 55 additions & 25 deletions

File tree

console/src/components/InstallPatternPage.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as React from 'react';
22
import Helmet from 'react-helmet';
33
import { useTranslation } from 'react-i18next';
4-
import { useHistory, useRouteMatch } from 'react-router-dom';
4+
import { useNavigateCompat } from '../hooks/useNavigateCompat';
5+
import { useParamsCompat } from '../hooks/useParamsCompat';
56
import {
67
ActionGroup,
78
Alert,
@@ -56,9 +57,8 @@ const PatternModel = {
5657

5758
export default function InstallPatternPage() {
5859
const { t } = useTranslation('plugin__patterns-operator-console-plugin');
59-
const history = useHistory();
60-
const match = useRouteMatch<{ name: string }>('/patterns/install/:name');
61-
const name = match?.params?.name;
60+
const navigate = useNavigateCompat();
61+
const { name } = useParamsCompat('/patterns/install/:name');
6262

6363
// Secret form state (integrated inline instead of separate page)
6464

@@ -251,11 +251,11 @@ export default function InstallPatternPage() {
251251
if (hasSecrets && vaultJobStatus?.status !== 'succeeded') return;
252252

253253
const timer = setTimeout(() => {
254-
history.push('/patterns');
254+
navigate('/patterns');
255255
}, 3000);
256256

257257
return () => clearTimeout(timer);
258-
}, [patternStatus, history, secretTemplate, secretFormData, vaultJobStatus]);
258+
}, [patternStatus, navigate, secretTemplate, secretFormData, vaultJobStatus]);
259259

260260
const handleSubmit = async () => {
261261
console.log('🚀 [InstallPatternPage] Starting pattern installation process');
@@ -410,7 +410,7 @@ export default function InstallPatternPage() {
410410
return t('Your pattern has been created. The operator is now reconciling it.');
411411
})()}
412412
</p>
413-
<Button variant="link" onClick={() => history.push('/patterns')}>
413+
<Button variant="link" onClick={() => navigate('/patterns')}>
414414
{t('Back to catalog')}
415415
</Button>
416416
</Alert>
@@ -612,7 +612,7 @@ export default function InstallPatternPage() {
612612
>
613613
{t('Install')}
614614
</Button>
615-
<Button variant="link" onClick={() => history.push('/patterns')}>
615+
<Button variant="link" onClick={() => navigate('/patterns')}>
616616
{t('Cancel')}
617617
</Button>
618618
</ActionGroup>

console/src/components/ManageSecretsPage.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as React from 'react';
22
import Helmet from 'react-helmet';
33
import { useTranslation } from 'react-i18next';
4-
import { useHistory, useRouteMatch } from 'react-router-dom';
4+
import { useNavigateCompat } from '../hooks/useNavigateCompat';
5+
import { useParamsCompat } from '../hooks/useParamsCompat';
56
import {
67
ActionGroup,
78
Alert,
@@ -29,9 +30,8 @@ import './SecretForm/SecretForm.css';
2930

3031
export default function ManageSecretsPage() {
3132
const { t } = useTranslation('plugin__patterns-operator-console-plugin');
32-
const history = useHistory();
33-
const match = useRouteMatch<{ name: string }>('/patterns/secrets/:name');
34-
const name = match?.params?.name;
33+
const navigate = useNavigateCompat();
34+
const { name } = useParamsCompat('/patterns/secrets/:name');
3535

3636
const [loading, setLoading] = React.useState(true);
3737
const [fetchError, setFetchError] = React.useState<string | null>(null);
@@ -171,7 +171,7 @@ export default function ManageSecretsPage() {
171171
<PageSection>
172172
<Alert variant="info" title={t('No secrets configured')}>
173173
<p>{t('This pattern does not have a secret template defined.')}</p>
174-
<Button variant="link" onClick={() => history.push('/patterns')}>
174+
<Button variant="link" onClick={() => navigate('/patterns')}>
175175
{t('Back to catalog')}
176176
</Button>
177177
</Alert>
@@ -234,7 +234,7 @@ export default function ManageSecretsPage() {
234234
<Button variant="primary" type="submit" isLoading={submitting} isDisabled={submitting}>
235235
{t('Inject Secrets')}
236236
</Button>
237-
<Button variant="link" onClick={() => history.push('/patterns')}>
237+
<Button variant="link" onClick={() => navigate('/patterns')}>
238238
{t('Back to catalog')}
239239
</Button>
240240
</ActionGroup>

console/src/components/PatternCatalogPage.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
22
import Helmet from 'react-helmet';
33
import { useTranslation } from 'react-i18next';
4-
import { useHistory } from 'react-router-dom';
4+
import { useNavigateCompat } from '../hooks/useNavigateCompat';
55

66
import {
77
Alert,
@@ -166,7 +166,7 @@ const TIER_DESCRIPTIONS: Record<string, string> = {
166166

167167
export default function PatternCatalogPage() {
168168
const { t } = useTranslation('plugin__patterns-operator-console-plugin');
169-
const history = useHistory();
169+
const navigate = useNavigateCompat();
170170
const [patterns, setPatterns] = React.useState<Pattern[]>([]);
171171
const [loading, setLoading] = React.useState(true);
172172
const [error, setError] = React.useState<string | null>(null);
@@ -488,7 +488,7 @@ export default function PatternCatalogPage() {
488488
<Button
489489
variant="secondary"
490490
onClick={() =>
491-
history.push(
491+
navigate(
492492
`/patterns/secrets/${pattern.catalogKey || pattern.name}`,
493493
)
494494
}
@@ -499,7 +499,7 @@ export default function PatternCatalogPage() {
499499
{isInstalled && (
500500
<Button
501501
variant="danger"
502-
onClick={() => history.push(`/patterns/uninstall/${pattern.name}`)}
502+
onClick={() => navigate(`/patterns/uninstall/${pattern.name}`)}
503503
>
504504
{t('Uninstall')}
505505
</Button>
@@ -515,7 +515,7 @@ export default function PatternCatalogPage() {
515515
variant="primary"
516516
isDisabled={isDisabled}
517517
onClick={() =>
518-
history.push(
518+
navigate(
519519
`/patterns/install/${pattern.catalogKey || pattern.name}`,
520520
)
521521
}

console/src/components/UninstallPatternPage.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import * as React from 'react';
22
import Helmet from 'react-helmet';
33
import { useTranslation } from 'react-i18next';
4-
import { useHistory, useRouteMatch } from 'react-router-dom';
4+
import { useNavigateCompat } from '../hooks/useNavigateCompat';
5+
import { useParamsCompat } from '../hooks/useParamsCompat';
56
import {
67
Alert,
78
Button,
@@ -29,9 +30,8 @@ const DELETION_PHASES: Record<string, { label: string; order: number }> = {
2930

3031
export default function UninstallPatternPage() {
3132
const { t } = useTranslation('plugin__patterns-operator-console-plugin');
32-
const history = useHistory();
33-
const match = useRouteMatch<{ name: string }>('/patterns/uninstall/:name');
34-
const name = match?.params?.name;
33+
const navigate = useNavigateCompat();
34+
const { name } = useParamsCompat('/patterns/uninstall/:name');
3535

3636
const [status, setStatus] = React.useState<PatternCRStatus | null>(null);
3737
const [deleting, setDeleting] = React.useState(false);
@@ -127,7 +127,7 @@ export default function UninstallPatternPage() {
127127
{deleted && (
128128
<Alert variant="success" title={t('Pattern successfully removed')}>
129129
<p>{t('The pattern and all its associated resources have been fully deleted.')}</p>
130-
<Button variant="link" onClick={() => history.push('/patterns')}>
130+
<Button variant="link" onClick={() => navigate('/patterns')}>
131131
{t('Back to catalog')}
132132
</Button>
133133
</Alert>
@@ -187,7 +187,7 @@ export default function UninstallPatternPage() {
187187
>
188188
{t('Confirm Uninstall')}
189189
</Button>
190-
<Button variant="link" onClick={() => history.push('/patterns')}>
190+
<Button variant="link" onClick={() => navigate('/patterns')}>
191191
{t('Cancel')}
192192
</Button>
193193
</div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as React from 'react';
2+
import * as ReactRouterDom from 'react-router-dom';
3+
4+
const hasUseNavigate = typeof (ReactRouterDom as any).useNavigate === 'function';
5+
6+
// React Router v6 removed useHistory in favor of useNavigate.
7+
// OCP 4.22+ ships v6; OCP 4.21 and earlier ship v5.
8+
export const useNavigateCompat: () => (path: string) => void = hasUseNavigate
9+
? () => {
10+
const navigate = (ReactRouterDom as any).useNavigate();
11+
return React.useCallback((path: string) => navigate(path), [navigate]);
12+
}
13+
: () => {
14+
const history = (ReactRouterDom as any).useHistory();
15+
return React.useCallback((path: string) => history.push(path), [history]);
16+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as ReactRouterDom from 'react-router-dom';
2+
3+
const isV6 = typeof (ReactRouterDom as any).useNavigate === 'function';
4+
5+
// React Router v5 (OCP < 4.22): useParams may not work if the console
6+
// framework doesn't expose route params through the standard context.
7+
// useRouteMatch explicitly matches the current URL against the given pattern.
8+
// React Router v6 (OCP 4.22+): useParams is the standard API.
9+
export const useParamsCompat: (pattern: string) => Record<string, string> = isV6
10+
? () => (ReactRouterDom as any).useParams()
11+
: (pattern: string) => {
12+
const match = (ReactRouterDom as any).useRouteMatch(pattern);
13+
return match?.params || {};
14+
};

0 commit comments

Comments
 (0)