Skip to content

Commit f98d0dc

Browse files
committed
ui: migrate router-aware navigation to hooks
1 parent 1bc598c commit f98d0dc

5 files changed

Lines changed: 74 additions & 78 deletions

File tree

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
import React from 'react';
2-
import { NavLink, NavLinkProps } from 'react-router-dom';
2+
import { NavLink, NavLinkProps, useLocation, useResolvedPath } from 'react-router-dom';
33
import { formatURL } from 'lib/utils';
44

5-
const CustomNavLink = (props: NavLinkProps) => (
6-
<NavLink
7-
isActive={(_, { pathname }) =>
8-
pathname.startsWith(
9-
typeof props.to === 'object' ? formatURL(props.to.pathname) : formatURL(props.to.toString().replace(/\?.+$/, ''))
10-
)
11-
}
12-
{...props}
13-
/>
14-
);
5+
interface Props extends Omit<NavLinkProps, 'className'> {
6+
activeClassName?: string;
7+
className?: string;
8+
}
9+
10+
const CustomNavLink = ({ activeClassName, className, to, ...props }: Props) => {
11+
const location = useLocation();
12+
const resolvedPath = useResolvedPath(to);
13+
const currentPath = formatURL(location.pathname);
14+
const targetPath = formatURL(resolvedPath.pathname);
15+
const isActive = targetPath ? currentPath.startsWith(targetPath) : currentPath === targetPath;
16+
const classNames = [className, isActive ? activeClassName : undefined].filter(Boolean).join(' ');
17+
18+
return <NavLink {...props} className={classNames} to={to} />;
19+
};
1520

1621
export default CustomNavLink;

ui/src/components/basic/LinkTabSelector/CommandsLinkTabSelector/CommandsLinkTabSelector.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import React from 'react';
2-
import { RouteComponentProps, withRouter } from 'react-router-dom';
2+
import { useLocation } from 'react-router-dom';
33
import styles from '../LinkTabSelector.module.scss';
44
import LinkTabSelector from 'components/basic/LinkTabSelector/LinkTabSelector';
55
import CustomNavLink from 'components/basic/CustomNavLink/CustomNavLink';
66
import { formatURL } from 'lib/utils';
77

8-
interface Props extends RouteComponentProps {
8+
interface Props {
99
className?: string;
1010
}
1111

1212
const CommandsLinkTabSelector = (props: Props) => {
13-
const currentPathArr = formatURL(props.location.pathname).split('/');
13+
const location = useLocation();
14+
const currentPathArr = formatURL(location.pathname).split('/');
1415
currentPathArr.length = currentPathArr.length - 2;
1516
const currentPath = currentPathArr.join('/');
1617

@@ -23,4 +24,4 @@ const CommandsLinkTabSelector = (props: Props) => {
2324
);
2425
};
2526

26-
export default withRouter(CommandsLinkTabSelector);
27+
export default CommandsLinkTabSelector;

ui/src/components/basic/LinkTabSelector/LogsLinkTabSelector/LogsLinkTabSelector.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import React from 'react';
2-
import { RouteComponentProps, withRouter } from 'react-router-dom';
2+
import { useLocation } from 'react-router-dom';
33
import styles from '../LinkTabSelector.module.scss';
44
import LinkTabSelector from 'components/basic/LinkTabSelector/LinkTabSelector';
55
import CustomNavLink from 'components/basic/CustomNavLink/CustomNavLink';
66
import { formatURL } from 'lib/utils';
77

8-
interface Props extends RouteComponentProps {
8+
interface Props {
99
className?: string;
1010
}
1111

1212
const LogsLinkTabSelector = (props: Props) => {
13-
const currentPathArr = formatURL(props.location.pathname).split('/');
13+
const location = useLocation();
14+
const currentPathArr = formatURL(location.pathname).split('/');
1415
currentPathArr.length = currentPathArr.length - 2;
1516
const currentPath = currentPathArr.join('/');
1617

@@ -23,4 +24,4 @@ const LogsLinkTabSelector = (props: Props) => {
2324
);
2425
};
2526

26-
export default withRouter(LogsLinkTabSelector);
27+
export default LogsLinkTabSelector;

ui/src/components/basic/LinkTabSelector/StackLinkTabSelector/StackLinkTabSelector.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import React from 'react';
2-
import { RouteComponentProps, withRouter } from 'react-router-dom';
2+
import { useLocation } from 'react-router-dom';
33
import styles from '../LinkTabSelector.module.scss';
44
import LinkTabSelector from 'components/basic/LinkTabSelector/LinkTabSelector';
55
import CustomNavLink from 'components/basic/CustomNavLink/CustomNavLink';
66
import { formatURL } from 'lib/utils';
77

8-
interface Props extends RouteComponentProps {
8+
interface Props {
99
className?: string;
1010
}
1111

1212
const StackLinkTabSelector = (props: Props) => {
13-
const currentPathArr = formatURL(props.location.pathname).split('/');
13+
const location = useLocation();
14+
const currentPathArr = formatURL(location.pathname).split('/');
1415
currentPathArr.length = currentPathArr.length - 2;
1516
const currentPath = currentPathArr.join('/');
1617

@@ -23,4 +24,4 @@ const StackLinkTabSelector = (props: Props) => {
2324
);
2425
};
2526

26-
export default withRouter(StackLinkTabSelector);
27+
export default StackLinkTabSelector;
Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,67 @@
11
import React from 'react';
22
import styles from './Breadcrumb.module.scss';
33
import Arrow from 'images/breadcrumb-arrow.svg';
4-
import { withRouter, RouteComponentProps } from 'react-router';
5-
import { Link } from 'react-router-dom';
4+
import { Link, useLocation } from 'react-router-dom';
65
import ErrorBoundary from 'components/basic/ErrorBoundary/ErrorBoundary';
76
import withDevSpaceConfig, { DevSpaceConfigContext } from 'contexts/withDevSpaceConfig/withDevSpaceConfig';
7+
import { formatURL } from 'lib/utils';
88

99
const capitalize = (s: string) => {
1010
if (typeof s !== 'string') return '';
1111
return s.charAt(0).toUpperCase() + s.slice(1);
1212
};
1313

14-
interface Props extends RouteComponentProps, DevSpaceConfigContext {}
15-
interface State {}
14+
interface Props extends DevSpaceConfigContext {}
1615

17-
class Breadcrumb extends React.Component<Props, State> {
18-
isComponentMounted: boolean = false;
16+
const Breadcrumb = (props: Props) => {
17+
const location = useLocation();
1918

20-
componentDidMount() {
21-
this.isComponentMounted = true;
22-
}
19+
const renderBreadcrumb = () => {
20+
const arrow = <img alt="" src={Arrow} />;
21+
const crumbs = formatURL(location.pathname)
22+
.split('/')
23+
.filter(Boolean);
24+
const breadcrumbs = crumbs.length > 1 ? crumbs.slice(0, crumbs.length - 1) : crumbs;
2325

24-
componentWillUnmount() {
25-
this.isComponentMounted = false;
26-
}
27-
28-
renderBreadcrumb = () => {
29-
const arrow = <img src={Arrow} />;
30-
31-
// Removes empty string "" because path starts with "/"
32-
const crumbs = this.props.match.url.split('/').slice(1);
33-
const crumbsWithIds = crumbs.slice(0, crumbs.length - 1);
34-
const params = this.props.match.path.split('/').slice(1);
35-
const crumbsWithParams = params.slice(0, params.length - 1);
26+
if (!breadcrumbs.length) {
27+
return null;
28+
}
3629

37-
if (crumbs.length === 1) {
30+
if (breadcrumbs.length === 1) {
3831
return (
3932
<span className={styles.crumb}>
4033
{arrow}
41-
<span className={styles.text}>{capitalize(crumbs[0])}</span>
34+
<span className={styles.text}>{capitalize(breadcrumbs[0])}</span>
4235
</span>
4336
);
4437
}
4538

46-
return [
47-
crumbsWithParams.map((crumb: string, idx: number) => {
48-
const isLastOne = idx === crumbsWithParams.length - 1;
49-
const shouldNotBeLink = false;
50-
51-
if (isLastOne || shouldNotBeLink) {
52-
return (
53-
<span className={styles.crumb} key={idx}>
54-
{arrow}
55-
<span className={styles.text}>{capitalize(crumb)}</span>
56-
</span>
57-
);
58-
}
39+
return breadcrumbs.map((crumb: string, idx: number) => {
40+
const isLastOne = idx === breadcrumbs.length - 1;
5941

42+
if (isLastOne) {
6043
return (
6144
<span className={styles.crumb} key={idx}>
6245
{arrow}
63-
<Link to={'/' + crumbsWithIds.slice(0, idx + 1).join('/')}>{capitalize(crumb)}</Link>
46+
<span className={styles.text}>{capitalize(crumb)}</span>
6447
</span>
6548
);
66-
}),
67-
];
49+
}
50+
51+
return (
52+
<span className={styles.crumb} key={idx}>
53+
{arrow}
54+
<Link to={'/' + breadcrumbs.slice(0, idx + 1).join('/')}>{capitalize(crumb)}</Link>
55+
</span>
56+
);
57+
});
6858
};
6959

70-
renderPrefix = () => {
71-
if (!this.props.devSpaceConfig.workingDirectory || !this.props.devSpaceConfig.config) {
60+
const renderPrefix = () => {
61+
if (!props.devSpaceConfig.workingDirectory || !props.devSpaceConfig.config) {
7262
return 'DevSpace';
7363
} else {
74-
const wd = this.props.devSpaceConfig.workingDirectory;
64+
const wd = props.devSpaceConfig.workingDirectory;
7565
// Unix
7666
const lastIdxOfSlash = wd.lastIndexOf('/');
7767
// Windows
@@ -85,22 +75,20 @@ class Breadcrumb extends React.Component<Props, State> {
8575
}
8676
};
8777

88-
renderRoute() {
78+
const renderRoute = () => {
8979
return (
9080
<React.Fragment>
91-
<div className={styles['account-selector']}>{this.renderPrefix()}</div>
92-
<div className={styles.crumbs}>{this.renderBreadcrumb()}</div>
81+
<div className={styles['account-selector']}>{renderPrefix()}</div>
82+
<div className={styles.crumbs}>{renderBreadcrumb()}</div>
9383
</React.Fragment>
9484
);
95-
}
85+
};
9686

97-
render() {
98-
return (
99-
<ErrorBoundary>
100-
<div className={styles.breadcrumb}>{this.renderRoute()}</div>
101-
</ErrorBoundary>
102-
);
103-
}
104-
}
87+
return (
88+
<ErrorBoundary>
89+
<div className={styles.breadcrumb}>{renderRoute()}</div>
90+
</ErrorBoundary>
91+
);
92+
};
10593

106-
export default withRouter(withDevSpaceConfig(Breadcrumb));
94+
export default withDevSpaceConfig(Breadcrumb);

0 commit comments

Comments
 (0)