I try to nest a route: I have a catalog of products in a Catalog component, which matches with url "backoffice/catalog".
I want to route to Edition component if the url matches with "backoffice/catalog/edit", but I need the Edition component to be a child of Catalog to share props.
I really don't understand why the nested route doesn't work, please save me ! And don't hesitate to tell me if anything is wrong with my App, I know JavaScript well, but I'm starting with React.
Here is my App component:
import React from "react";
import { Route, Switch } from "react-router-dom";
import { Home } from "./components/Static/Home.js";
import { Dashboard } from "./components/Backoffice/Dashboard.js";
import { Catalog } from "./components/Backoffice/catalog/Catalog.js";
import { Login } from "./components/Login/Login.js";
import { Signup } from "./components/Signup/Signup.js";
import { PrivateRoute } from "./components/PrivateRoute.js";
import "./scss/App.scss";
import {Header} from "./components/Structure/Header";
import {BOHeader} from "./components/Structure/Backoffice/Header";
import {List} from "./components/Listing/List";
function App()
{
return (
<div className="App">
<div className="App-content">
<Switch>
<Route path='/backoffice' component={BOHeader} />
<Route path='/' component={Header} />
</Switch>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/login' component={Login} />
<Route exact path='/signup' component={Signup} />
<Route path='/listing' component={List}/>
<PrivateRoute exact path='/backoffice' component={Dashboard}/>
<PrivateRoute exact path='/backoffice/catalog' component={Catalog}/>
</Switch>
</div>
</div>
);
}
export default App;
Here is my Catalog component (the route is made in the render method:
import React from 'react';
import Data from '../../../Utils/Data';
import {Product} from './Product';
import {Edition} from './Edition';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useRouteMatch,
useParams
} from "react-router-dom";
export class Catalog extends React.Component
{
state = {
title: '',
products: [],
editionProduct: null
};
obtainProducts = () =>
{
Data.products.obtain()
.then(products => {this.setState({products: products});})
};
editProductHandler = product =>
{
this.setState({editionProduct: product});
};
saveProductHandler = product =>
{
Data.products.save(product).then(() => {
this.state.products.map(item => {
item = item._id === product._id ? product : item;
return item;
})
});
};
deleteProductHandler = event =>
{
const productId = event.target.closest('.product-actions').dataset.productid;
let products = this.state.products.filter(product => {
return product._id !== productId;
});
this.setState({products: products}, () => {
Data.products.remove(productId);
});
};
displayProducts = () =>
{
return this.state.products.map(product => {
return (
<li key={product._id} className='catalog-item'>
<Product
deleteProductHandler={this.deleteProductHandler}
editProductHandler={this.editProductHandler}
data={product}
/>
</li>
)
});
};
componentWillMount()
{
this.obtainProducts();
}
render() {
const Products = this.displayProducts();
let { path, url } = useRouteMatch();
return (
<div className={this.state.editionProduct ? 'catalog edit' : 'catalog'}>
<h1>Catalog</h1>
<Switch>
<Route exact path={path}>
<ul className='catalog-list'>{Products}</ul>
</Route>
<Route path={`${path}/edit`}>
<Edition saveProductHandler={this.saveProductHandler} product={this.state.editionProduct} />
</Route>
</Switch>
</div>
);
}
}
Any ideas?
If the error persists, try to delete your node_modules and package-lock. json (not package. json ) files, re-run npm install and restart your IDE. The error is often caused by having multiple versions of react in the same project.
This problem can also come up when you use npm link or an equivalent. In that case, your bundler might “see” two Reacts — one in application folder and one in your library folder. Assuming myapp and mylib are sibling folders, one possible fix is to run npm link ../myapp/node_modules/react from mylib .
You can not use hooks outside a component function, it is simply how they work. But, you can make a composition of hooks. React relies on an amount and order of how hooks appear in the component function.
"Hooks can only be called inside the body of a function component" ReactJS Error. "Hooks can only be called inside the body of a function component." This is a common warning that developers run into when starting out with hooks in React.
You can't use hooks inside Catalog
component because it is a class component. So you have two ways to resolve your issue:
useRouteMatch
inside Catalog
component. If you need to get match
data inside a component, you need to use withRouter
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:
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
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();
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);
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"
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With