If I type in a URL that doesn't exist in my app, it sends me to a custom 404 Not Found page.
However, when I wrap my Navbar with <User.Provider value={value}> .... </User.Provider>, the 404 Not Found component appears on all pages.
Basically, I don't want the NoMatch component to appear on all other pages, only when a url does not exist/match with my app's navbar.
So if I type in localhost:5000/this-does-not-exist-12345212wergdv
it should take me to the custom component, NoMatch.
Code sandbox link.
https://codesandbox.io/s/delicate-sea-b2r96?file=/src/App.js
Anyone know how I can solve this? Thanks for any help.
My code below.
UserContext.jsx code
import React from "react";
export const User = React.createContext(null);
Home.jsx code
import React from "react";
import {User} from "../auth/UserContext";
const Home = props => {
const {user} = React.useContext(User);
return (
<div>
<h4>This is Home page</h4>
<h4>User: {JSON.stringify({user})}</h4>
</div>
);
}
export default Home;
Navbar.jsx code
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Switch,
Redirect,
useLocation,
useHistory
} from "react-router-dom";
import {User} from "../auth/UserContext";
import Home from "../pages/Home";
const Navbar = React.memo(props => {
const [user, setUser] = React.useState(null);
const value = React.useMemo(() => ({user, setUser}), [user, setUser]);
const history = useHistory();
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/old-match">Old Match, to be redirected</Link>
</li>
<li>
<Link to="/will-match">Will Match</Link>
</li>
<li>
<Link to="/will-not-match">Will Not Match</Link>
</li>
<li>
<Link to="/also/will/not/match">Also Will Not Match</Link>
</li>
</ul>
<Switch>
<User.Provider value = {value}>
<Route exact path="/">
<Home />
</Route>
<Route path="/old-match">
<Redirect to="/will-match" />
</Route>
<Route path="/will-match">
<WillMatch />
</Route>
<Route path="*">
<NoMatch />
</Route>
</User.Provider>
</Switch>
</div>
</Router>
);
})
function WillMatch() {
return <h3>Matched!</h3>;
}
function NoMatch() {
let location = useLocation();
return (
<div>
<h3>
No match for <code>{location.pathname}</code>
</h3>
</div>
);
}
export default Navbar;
App.jsx page
import React from "react";
import Navbar from "./components/Navbar";
export default function App(props) {
return (
<div>
<Navbar/>
</div>
);
}
The explanation is that the Switch component looks only at its direct children for the path property regardless of whether or not they are Routes.
In this case, since the direct child of Switch is User.Provider with no path property the switch will always just render that child (a switch will always render at least one child).
Now the children of User.Provider will be rendered, and the Switch will no longer have any bearing. This means now that the <Route path="*" /> will ALWAYS render like you are seeing.
Solution:
The solution is to just move the location of the User.Provider to surround the Switch component.
const Navbar = React.memo(props => {
const [user, setUser] = React.useState(null);
const value = React.useMemo(() => ({user, setUser}), [user, setUser]);
const history = useHistory();
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/old-match">Old Match, to be redirected</Link>
</li>
<li>
<Link to="/will-match">Will Match</Link>
</li>
<li>
<Link to="/will-not-match">Will Not Match</Link>
</li>
<li>
<Link to="/also/will/not/match">Also Will Not Match</Link>
</li>
</ul>
<User.Provider value = {value}>
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/old-match">
<Redirect to="/will-match" />
</Route>
<Route path="/will-match">
<WillMatch />
</Route>
<Route path="*">
<NoMatch />
</Route>
</Switch>
</User.Provider>
</div>
</Router>
);
})
Related
I am Trying to reach the <Gallery/> Component using a Menu button with React-Router Link
so the code is for the Menu
Menu.jsx
import { Link } from 'react-router-dom';
export default function Menu({ menuOpen, setMenuOpen }) {
return (
<div className={"menu " + (menuOpen && "active")}>
<ul>
<li onClick={() => setMenuOpen(false)}>
<Link to="/">Home Page</Link>
</li>
<li onClick={() => setMenuOpen(false)}>
<Link to="/Gallery">Gallery</Link>
</li>
</ul>
</div>
);
}
and the code for APP.jsx:
import './App.scss';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { useState } from 'react';
import Gallery from './components/Gallery/Gallery';
import Menu from './components/menu/Menu';
import Topbar from './components/topbar/Topbar';
import FooterComp from './components/Footer/FooterComp';
const App = () => {
const [menuOpen, setMenuOpen] = useState(false);
return (
<>
<Router>
<Topbar menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
<Menu menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/Gallery" elemtent={<Gallery />} />
</Routes>
<FooterComp />
</Router>
</>
)
}
export default App
When I click the button which is supposed to route to the <Gallery/> Component it routes to an empty component and this warning is displayed in the console
Matched leaf route at location "/Gallery" does not have an element. This means it will render an with a null value by default resulting in an "empty" page.
I searched for this problem and only router-dom version related fixes are there and you can see I'm using the correct v6 props and components.
You have a typo. element
Change
<Route path="/Gallery" elemtent={<Gallery />} />
to
<Route path="/Gallery" element={<Gallery />} />
The contents are as follows.
<Route path="/:id" >
<Navigate
to="/intro?Id=:id"
/>
</Route>
Is there a way to pass the :id parameter to the Route tag as the :id parameter to the Navigate tag like this?
OK, I see, so you want to render essentially a redirect from "/:id" to "intro?Id=:id".
You can't just redirect from one path to the other since you are dynamically using the id route match param as part of a querystring parameter, and since react-router-dom doesn't deal with the URL querystring you need to handle this mapping yourself. It took a bit of digging through the version 6 docs but this is what I've come up with:
Create a custom component to read the match param and render the Navigate component.
const IntroRedirect = () => {
const { id } = useParams();
return <Navigate replace to={`/intro?Id=${id}`} />
};
Render MyNavigate on a Route.
<Route path="/:id" element={<IntroRedirect />} />
Full demo code:
import {
BrowserRouter as Router,
Link,
Routes,
Route,
Navigate,
useLocation,
useParams
} from "react-router-dom";
const IntroRedirect = () => {
const { id } = useParams();
return <Navigate replace to={`/intro?Id=${id}`} />;
};
const Intro = () => {
const { search } = useLocation();
const query = new URLSearchParams(search);
const Id = query.get("Id");
return <div>Intro: Id="{Id}"</div>;
};
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Router>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/1234">"/1234"</Link>
</li>
<li>
<Link to="/4567">"/4567"</Link>
</li>
<li>
<Link to="/7890">"/7890"</Link>
</li>
</ul>
<Routes>
<Route path="/intro" element={<Intro />} />
<Route path="/:id" element={<IntroRedirect />} />
</Routes>
</Router>
</div>
);
}
I have a problem with nested react-router-dom. I want to get out of inner router to outer router. I don't know how to explain it, so I bring a example here.
https://codesandbox.io/s/react-router-preventing-transitions-forked-vmt4c?fontsize=14&hidenavigation=1&theme=dark
What I want to do here is route back to '/' (BlockingForm component) from Topics component by clicking go root button.
How can I solve this?
import React, { useState } from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Prompt,
useParams,
useRouteMatch
} from "react-router-dom";
// Sometimes you want to prevent the user from
// navigating away from a page. The most common
// use case is when they have entered some data
// into a form but haven't submitted it yet, and
// you don't want them to lose it.
export default function PreventingTransitionsExample() {
return (
<Router>
<ul>
<li>
<Link to="/">Form</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/one">One</Link>
</li>
<li>
<Link to="/two">Two</Link>
</li>
</ul>
<Switch>
<Route path="/" exact children={<BlockingForm />} />
<Route path="/home" exact children={<NestingExample />} />
<Route path="/one" children={<h3>One</h3>} />
<Route path="/two" children={<h3>Two</h3>} />
</Switch>
</Router>
);
}
function NestingExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="home/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/home/">
<Home />
</Route>
<Route path="/home/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function Topics() {
// The `path` lets us build <Route> paths that are
// relative to the parent route, while the `url` lets
// us build relative links.
let { path, url } = useRouteMatch();
return (
<div>
<h2>Topics</h2>
<ul>
<li>
<Link to={`${url}/rendering`}>Rendering with React</Link>
</li>
<li>
<Link to={`${url}/components`}>Components</Link>
</li>
<li>
<Link to={`${url}/props-v-state`}>Props v. State</Link>
</li>
<li>
<Link to={"/"}>Go root</Link>
</li>
</ul>
<Switch>
<Route exact path={path}>
<h3>Please select a topic.</h3>
</Route>
<Route path={`${path}/:topicId`}>
<Topic />
</Route>
</Switch>
</div>
);
}
function Topic() {
// The <Route> that rendered this component has a
// path of `/topics/:topicId`. The `:topicId` portion
// of the URL indicates a placeholder that we can
// get from `useParams()`.
let { topicId } = useParams();
return (
<div>
<h3>{topicId}</h3>
</div>
);
}
function BlockingForm() {
let [isBlocking, setIsBlocking] = useState(false);
return (
<form
onSubmit={(event) => {
event.preventDefault();
event.target.reset();
setIsBlocking(false);
}}
>
<Prompt
when={isBlocking}
message={(location) =>
`Are you sure you want to go to ${location.pathname}`
}
/>
<p>
Blocking? {isBlocking ? "Yes, click a link or the back button" : "Nope"}
</p>
<p>
<input
size="50"
placeholder="type something to block transitions"
onChange={(event) => {
setIsBlocking(event.target.value.length > 0);
}}
/>
</p>
<p>
<button>Submit to stop blocking</button>
</p>
</form>
);
}
You can achieve that by using the useHistory to manipulate the parent router (for react router v5)
reference:
https://v5.reactrouter.com/web/api/Hooks/usehistory
I've changed only the nested router in order to show you how to change the parent router location :)
import React, { useState } from "react";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Prompt,
useParams,
useRouteMatch,
useHistory
} from "react-router-dom";
export default function PreventingTransitionsExample() {
return (
<Router>
<ul>
<li>
<Link to="/">Form</Link>
</li>
<li>
<Link to="/home">home</Link>
</li>
<li>
<Link to="/one">One</Link>
</li>
<li>
<Link to="/two">Two</Link>
</li>
</ul>
<Switch>
<Route path="/" exact children={<BlockingForm />} />
<Route path="/home" exact children={<NestingExample />} />
<Route path="/one" children={<h3>One</h3>} />
<Route path="/two" children={<h3>Two</h3>} />
</Switch>
</Router>
);
}
function NestingExample() {
//use useHistory hook to get the history context from parent router
const history = useHistory();
//use this function on a onClick event instead of <link> to change the parent router
const changeParentRouter = (url) => history.push(url);
return (
<Router>
<div>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="home/topics">Topics</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/home/">
<Home />
</Route>
<Route path="/home/topics">
<Topics />
</Route>
</Switch>
</div>
</Router>
);
}
For react router v6, use the useNavigate hook:
import { useNavigate } from "react-router-dom";
function useLogoutTimer() {
const userIsInactive = useFakeInactiveUser();
const navigate = useNavigate();
return <div>
<button onClick={() => navigateTo('/')}>
Dashboard
</button>
</div>
}
Reference: https://reactrouter.com/en/6.4.4/hooks/use-navigate
I am trying to make a Navbar but the isn't re-directing to the given page. If I click any of the links in the Navbar, it would change the path in the url bar but won't re-direct to that page. I am not sure if I am missing anything. When I replace it with the tags, it works perfectly.
Navbar.js
import React from "react";
import { BrowserRouter as Router, Link, Switch } from "react-router-dom";
const Navbar = () => {
return (
<Router>
<Switch>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/articles">Articles</Link>
</li>
<li>
<Link to="/articles-all">All articles</Link>
</li>
</ul>
</nav>
</Switch>
</Router>
);
};
export default Navbar;
App.js
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import './App.css'
//pages
import Home from "./Pages/Home";
import About from "./Pages/About";
import Articles from "./Pages/Articles";
import ArticlesList from "./Pages/ArticlesList";
//components
import Navbar from './components/Navbar';
const App = () => {
return (
<div>
<Navbar/>
<Navigation />
</div>
);
};
export default App;
const Navigation = () => {
return (
<Router>
<div id="page-body">
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/articles" component={Articles} />
<Route path="/articles-all" component={ArticlesList} />
</div>
</Router>
);
};
Since you define the Router within Navigation and another one in Navbar your Links are not able to communicate to the Router Component in Navigation as they just communicate to their nearest parent Router component
You must you use a single Router instance to be able to perform seemless navigation within your App. Also a Switch component is not needed with Links but with Route
const App = () => {
return (
<Router>
<Router component={Navbar}/> // rendered as default route so that they receive router props
<Router component={Navigation} />
</Router>
);
};
export default App;
const Navigation = () => {
return (
<div id="page-body">
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/articles" component={Articles} />
<Route path="/articles-all" component={ArticlesList} />
</div>
);
};
const Navbar = () => {
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/articles">Articles</Link>
</li>
<li>
<Link to="/articles-all">All articles</Link>
</li>
</ul>
</nav>
);
};
export default Navbar;
Here's a working codesandbox URL https://codesandbox.io/s/frosty-black-3i8hp?file=/src/App.js
You were wrapping links with browserRouter and Switch. These APIs are intended to wrap Routes only.
So, It wasn't able to communicate well with your react app.
I want to access the match params on the navigation on my app, I'm using react and react-router-dom. Here is a code snipped for simple example I made, as you can see on the Topics component I get the correct match at component level but not in the nav bar, I do get the correct url path in the location prop so I'm not sure if I'm doing this right.
This would be the working snippet, since I couldn't add react-router-dom to the stack overflow snippet.
import { BrowserRouter as Router, Route, Link, withRouter } from "react-router-dom";
const BasicExample = (props) =>
<Router>
<div>
<Nav/>
<hr />
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/topics" component={Topics} />
</div>
</Router>;
const About = () => (
<div>
<h2>About</h2>
</div>
);
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
const Navigation = (props) => (
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
<li>{`match prop -> ${JSON.stringify(props.match)}`}</li>
<li>{`location prop -> ${JSON.stringify(props.location)}`}</li>
</ul>
);
const Nav = withRouter(Navigation);
const Topic = ({ match, location }) => (
<div>
<h3>{match.params.topicId}</h3>
<li>{`match prop -> ${JSON.stringify(match)}`}</li>
<li>{`location prop -> ${JSON.stringify(location)}`}</li>
</div>
);
const Topics = ({ match, location, history }) => (
<div>
<h2>Topics</h2>
<li>{`match prop -> ${JSON.stringify(match)}`}</li>
<li>{`location prop -> ${JSON.stringify(location)}`}</li>
<ul>
<li>
<Link to={`${match.url}/rendering`}>
Rendering with React
</Link>
</li>
<li>
<Link to={`${match.url}/components`}>
Components
</Link>
</li>
<li>
<Link to={`${match.url}/props-v-state`}>
Props v. State
</Link>
</li>
</ul>
<Route path={`${match.url}/:topicId`} component={Topic} />
<Route
exact
path={match.url}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
ReactDOM.render(<BasicExample />, document.getElementById("root"));
<body>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
</body>
For detecting :topicId in <Nav> use matchPath imported from react-router:
import { matchPath } from 'react-router'
matchPath(location.pathname, {
path:'/topics/:topicId',
exact: true,
strict: false
}})
This is a little outdated now. Can be achieved with react-router-dom by simply doing something like:
import { withRouter } from 'react-router-dom';
console.log(props.match.params);
Don't forget to wrap the component inside withRouter
export default withRouter(YourComponent);
props.match.params will return an object with all the params.