react-router-dom: Invalid hook call, Hooks can only be called inside of the body of a function component
You can't use hooks inside Catalog
component because it is a class component. So you have two ways to resolve your issue:
- Rewrite your component from class to functional.
- Do not use
useRouteMatch
insideCatalog
component. If you need to getmatch
data inside a component, you need to usewithRouter
high-order component.
So if you select second way, you will need to wrap your Catalog
component in withRouter
:
export default withRouter(Catalog);
Change one row in render
function from:
let { path, url } = useRouteMatch();
To:
const { path, url } = this.props.match;
And do not forget to change the import of your Catalog
component, because now your component exports as default.
As I had the same issue when setting up my React Router with Typescript, I will detail a little bit more Andrii answer in 4 steps:
1 - npm/yarn packages
yarn add react-router-dom --save
yarn add @types/react-router-dom --save-dev
or
npm install react-router-dom --save
npm install @types/react-router-dom --save-dev
2 - index.tsx
1) When importing your higher order component (App in the present case), do not use curly brackets as App will be exported as default;
2) BrowserRouter needs to be in a upper level rather the class that will be exported as "default withRouter(Class)", in order to prevent the following error:
"You should not use Route or withRouter() outside a Router"
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import * as serviceWorker from './serviceWorker';
import App from './app';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
serviceWorker.unregister();
3 - app.tsx
1) Import from react-router-dom, withRouter & RouteComponentProps (or your own PropType definition);
2) Extend React.Component and use the RouteComponentProps interface;
3) Pass the props to components you want to share routing data;
4) Export the higher order class as default withRouter.
import React, { ReactElement } from 'react';
import { Switch, Route, withRouter, RouteComponentProps } from 'react-router-dom';
import { ExpensesPage } from './pages/expenses/expenses.page';
import { HomePage } from './pages/home/home.page';
import { HeaderComponent } from './components/header/header.component';
import './app.scss';
class App extends React.Component<RouteComponentProps> {
public render(): ReactElement {
return (
<div className='playground'>
<HeaderComponent {...this.props} />
<div className="playground-content">
<Switch>
<Route exact path='/' component={HomePage} {...this.props} />
<Route exact path='/expenses' component={ExpensesPage} {...this.props} />
</Switch>
</div>
</div>
);
}
}
export default withRouter(App);
4 - header.component
Through your RouteComponentProps extending your class, you can access normally the routing props as history, location and match as bellow:
import React, { ReactElement } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import './header.component.scss';
export class HeaderComponent extends React.Component<RouteComponentProps> {
public render(): ReactElement {
const { location } = this.props;
console.log(location.pathname);
return (
<header className="header">
{/* ... */}
</header >
);
}
}
Hope it helps because I had a bit of challenge to make this works in a simple environment with webpack and no redux. Last time working properly with the following versions:
{
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-router-dom": "^5.1.2",
"sass-loader": "^8.0.2",
"style-loader": "^1.1.3",
"typescript": "^3.8.2",
"webpack": "^4.41.6",
"webpack-dev-server": "^3.10.3",
},
{
"@types/react-router-dom": "^5.1.3",
"webpack-cli": "^3.3.11"
}