I am using react router for a SPA Dashboard. I have these two routes(Routes.js) in dashboard right now. Also, a separate route in App.js for landing page.
Now it works fine as expected when it loads and when I click different routes. For example, When I click customers route(in sidebar), it open the customers section on the same page in right side. But, when I refresh the customers page localhost:5000/customers, now it opens as a separate page. How can I solve this.
I tried debugging it but still no luck.
Here's an example : https://github.com/gouravthakur39/MRE
Unfortunately can't reproduce it in code sandbox
Routes.js
import React from "react";
import { Route, Switch } from "react-router-dom";
import Dashboard from "../pages/Dashboard";
import Customers from "../pages/Customers";
const Routes = () => {
return (
<Switch>
<Route path="/home" exact component={Dashboard} />
<Route path="/customers" exact component={Customers} />
</Switch>
);
};
export default Routes;
Part of App.js
import { Fragment } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import PrivateRoute from "./private/PrivateRoute";
import Landing from "./pages/Landing";
import Home from "./pages/Home";
import Customers from "./pages/Customers";
function App() {
return (
<Fragment>
<div className="App">
<BrowserRouter>
<Switch>
<Route exact path="/" component={Landing} />
<PrivateRoute exact path="/home" component={Home} />
<PrivateRoute exact path="/customers" component={Customers} />
</Switch>
</BrowserRouter>
</div>
</Fragment>
);
}
export default App;
Question/Issue
Based on your code and the description on the Dashboard component it sounds like you want Dashboard and Customers to render "nested" in the "/home" path.
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
<p>I want to open dashboard and customers here when clicked</p>
<p>
Right now, when you click customers, it will open it in new page and not
here.
</p>
</div>
);
};
This is because Home is rendering the Dashboard on the same "/home" path, and the sidebar is linking to a "/customers" path at the root router level. This Customers component is outside the nested home/dashboard component.
Solution
App
For this the root "/home" in App needs to not exactly match in order for it to render nested routes. Remember also that in the Switch component that path order and specificity matter. Order the paths from more specific to less specific, i.e. "/home" is more specific than "/" and should be ordered higher/before. The "/customers" route/path should be removed since it will be rendered by Home.
function App() {
return (
<Fragment>
<BrowserRouter>
<Switch>
<Route path="/home" component={Home} />
<Route path="/" component={Landing} />
</Switch>
</BrowserRouter>
</Fragment>
);
}
Routes (rendered by Home)
Use the useRouteMatch hook to gather the current matched path value and build the nested routes. Remember the path order and specificity rule for the Switch.
import { Route, Switch, useRouteMatch } from "react-router-dom";
const Routes = () => {
const { path } = useRouteMatch();
return (
<Switch>
<Route path={`${path}/customers`} component={Customers} /> // "/home/customers"
<Route path={path} component={Dashboard} /> // "/home"
</Switch>
);
};
Sidebar (rendered by Home)
Use the useRouteMatch hook to gather the current matched url value and build the nested links.
import { Link, useRouteMatch } from "react-router-dom";
const Sidebar = () => {
const { url } = useRouteMatch();
return (
<div>
<ul>
<li>
<Link to={url}>Dashboard</Link> // to "/home"
</li>
<li>
<Link to={`${url}/customers`}>Customers</Link> // to "/home/customers"
</li>
</ul>
</div>
);
};
Related
I have created one simple project with 3 main components/pages.
Home page (path: "/") with is login page. once we click on login button, will create a dynamic URL "admin/random_num
LogIn.js
import { Link, Route } from "react-router-dom";
import Dashboard from "../Admin/Dashboard";
export default function Login(props) {
return (
<div>
<h1>Login form</h1>
<Link to={`/admin/${Math.random()}`}>Login Button</Link>
<Route path="/admin/:aid" component={Dashboard} />
</div>
);
}
2nd component/page is admin(path: "/admin/randum_num" , eg: "/admin/132"), which should be displayed when clicked on login button on login page.
Admin.js
export default function Dashboard(props) {
return (
<div>
<h1>Admin Dashboard</h1>
</div>
)
}
3rd component is 404page, which will simply display when no route is matched.
Error404.js
export default function Error404() {
return (
<div>
<h1>404</h1>
</div>
)
}
last not the least, in App.js i am setting up routing.
App.js
import React from 'react'
import './App.css';
import Error404 from './pages/Error404';
import Login from './pages/StartingScreen/Login';
function App() {
return (
<BrowserRouter>
<div className="App">
<Switch>
<Route exact path="/" component={Login} />
<Route component={Error404} />
</Switch>
</div>
</BrowserRouter>
);
}
export default App;
Note: i am able to display the admin dashboard page if i remove "exact" parameter in Route statement but after removing "exact" both Login and Dashboard page will be visible which i dont want. I want only one component should be visible at given route.
I am struggling to find an answer regarding this problem since one week, kindly help. If the given detail is incomplete, let me know.
Since you are using exact for your login component, that means your Login component will only render for the root path /.
And your Dashboard component is inside your Login component, that means you also need to add "/admin/:aid" to your login route.
You need to change this
<Route exact path="/" component={Login} />
to
<Route exact path={["/", "/admin/:aid"]} component={Login} />
Update:
To hide login button, you can use another Switch in your Login component
export default function Login(props) {
return (
<div>
<Switch>
<Route exact path="/">
<h1>Login form</h1>
<Link to={`/admin/${Math.random()}`}>Login Button</Link>
</Route>
<Route exact path="/admin/:aid" component={Dashboard} />
</Switch>
</div>
);
}
I am trying to setup the PageNotFound. The 2nd route should match only for ids ['1', '2', '3']; (these id values hwoever i get after the api call. So i save the values in redux store from inside the Home component). Now when i try and access a incorrect id then it should redirect to PageNotFound. If id in url is lets say 4 it should redirect to PageNotFound.
Do i impletement this is User component when it gets redirected or is there a way to set it up in the below App.js file itself ?
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import Home from "./Pages/Home";
import User from "./Pages/User";
import PageNotFound from "./Pages/PageNotFound";
import "./App.css";
function App() {
return (
<div className="App">
<Router>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/user/:id" exact component={User} />
<Route path="*" exact component={PageNotFound} />
</Switch>
</Router>
;
</div>
);
}
export default App;
You may create generic component for validation purpose.
Solution should be like this
<Route path="/user/:id" exact component={MyCheckComponent} />
MyCheckComponent.jsx
const MyCheckComponent =(props) => {
const {id} = useParams()
const allowedIds = useSelector(s => s.user.allowedIds)
return allowedIds.includes(id) ? <User {...props} /> : <Redirect to="/not-found"/>
}
At the end you may eliminate MyCheckComponent using render prop of the route.
I have following route in BrowserRouter and Switch
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
<Router>
<Switch>
<Route exact path="/" component={HomeScreen}/>
<Route exact path="/about" component={AboutScreen}/>
<Route exact path="/products" component={ProductScreen}/>
<Route path="/product/:id" component={SingleProductScreen}/>
</Router>
</Switch>
All links are working perfectly in localhost. Except on the last route, nothing renders (not even 404 page) when I npm run build and then deploy.
I'm using react-router-dom for routing
Edit: This is what I get on my domain.com/product/1
This is my SingleProductScreen.js:
import React, { useEffect, useState } from 'react'
const SingleProductScreen = ({ match }) => {
const id = match.params.id;
return (
<div className="App">
<h1 className="title">{`Product Number: ${id}`}</h1>
</div>
)
}
export default SingleProductScreen
Once you build in react it will generate all static file.
For Dynamic routing it is more like
const App = () => (
<BrowserRouter>
{/* here's a div */}
<div>
{/* here's a Route */}
<Route path="/products" component={ProductScreen}/>
</div>
</BrowserRouter>
)
// when the url matches `/:id` this component renders
const ProductScreen= ({ match }) => (
// here's a nested div
<div>
{/* here's a nested Route,
match.url helps us make a relative path */}
<Route
path={match.url + '/:id'}
component={SingleProductScreen}
/>
</div>
)
Hello in my directions file I set my struct
header
navbar
and my switch
foote
const AppRouter = () => (
<BrowserRouter>
<Route path="/login" component={AuthPage} exact={true} />
<Route path="/dashboard/addProduct" component={AddProduct} exact={true} />
<div>
<Header/>
<Navigation/>
<Container maxWidth="lg" >
<Switch>
<Route path="/" component={LandingPage} exact={true} />
<Route path="/xd" component={AuthPage} exact={true} />
<Route component={NotFoundPage} />
</Switch>
</Container>
</div>
</BrowserRouter>
);
But I have two routes where I didn't want to show my header
footer
and nav bar
which are the login and addproduct routes
how could i do that?
This is a little bit hacky (just to match your current approach) since I'm assuming you just want to hide those components in only those 2 routes, You can use withRouter in your Header and Navigation components, withRouter will provide to you the location prop and you can have a condition like this:
import React from "react";
import { withRouter } from "react-router-dom";
const Navigation = props => {
const { location } = props;
const { pathname } = location;
if (pathname !== "/login" || pathname !== "/dashboard/addProduct" ) {
return (
// Component logic goes here
);
}
// if we're on those routes we should return null to not render anything
return null;
};
export default withRouter(Navigation);
If you want a more robust long term approach this answer could help:
How to hide navbar in login page in react router
I am trying to navigate to /movie/:title on which I am rendering a new page. But it only renders if my movie page i.e /movie renders, but I don't want my movie page to be mounted on navigating to /movie/:title. I only want it as part of the path. Making the /movie path exact doesn't work as /movie/:title wouldn't render with /movie as exact.
App.js
class App extends React.Component {
render() {
return (
<div>
<Header />
<Route path='/movies' component={Movies} />
<Route path='/tvshows' component={TvShow} />
</div>
)}
Movie.js
return (
<div className="movies">
<Route path={`${match.path}`} render={(props) => <CollectionGrid movies/>} />
<Route path={`${match.path}`} render={(props) => <CollectionOverview movies/>} />
<Route path={`${match.path}/:title`} component={ItemPage} />
</div>
);
A couple of notes. Conventionally, you should keep all your routes in a single router, thus making it easier to navigate and identify your application.
Organize your Router like this:
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./Home";
import Movies from "./Movies";
import ItemPage from "./ItemPage";
const App = () => {
return (
<BrowserRouter>
<div>
<Switch>
<Route path="/" component={Home} exact />
<Route path="/movies/:title" component={ItemPage} />
<Route path="/movies" component={Movies} />
</Switch>
</div>
</BrowserRouter>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Additionally, to resolve your solution, you would need the Switch component, which is available in react-router-dom. The Switch component, will look through a list of Routes and will only render the FIRST Route who's path string is included within the URL string.
In the list above we have 3 Routes. If your URL is "/movies/blah", it will only render the ItemPage component, because that Route's path was matched first within the list.
We moved the "/movies/:title" Route before the regular "/movies" for this very reason. If the "/movies" Route appeared first in the list and if your URL was "/movies/blah", it would still satisfy the "/movies" Route. That means Switch would only render Movies component, which is not what you want.
See working sandbox: https://codesandbox.io/s/hopeful-bogdan-lzl76
you are not able to access to match direct in this app the match is found in this .props.match
you cand used it in
const App({match})
then when you need to used it should
<Route path={`${match.path}`} render={(props) => <CollectionGrid movies/>} />
look to this example and explain