Nested "Redirect" in React-Router-DOM - reactjs

I'm new to React JS and now I'm trying to learn how to use "react-router-dom". Here I have a very simple app and I'm trying to fix one issue. So, there are 4 pages (Main Page / First Page / Second Page / Third Page wih Items ). We can ignore the first 3 pages and focus on the last one - Third Page wih Items. There we have 3 items. I used <Redirect /> to make First Item content visible immediately after users click on Third Page wih Items and it seems to work fine but there is a problem... First time you click on Third Page wih Items, First Item is shown as expected. But if you are still inside Third Page wih Items and you click on it again, First Item disappears and you basically need to reload the page or go to another page and back.
My question is - What should I do make First Item stays even if users click on Third Page wih Items multiple times in a row?
import React from "react";
import { Route, NavLink, Redirect } from "react-router-dom";
import "./App.css";
const Header = () => {
return (
<ul>
<li>
<NavLink to="/">Main Page</NavLink>
</li>
<li>
<NavLink to="/first">First Page</NavLink>
</li>
<li>
<NavLink to="/second">Second Page</NavLink>
</li>
<li>
<NavLink to="/third-with-items">Third Page wih Items</NavLink>
</li>
</ul>
);
};
const Main = () => <h1>Main Page</h1>;
const First = () => {
return <h3>First Page Content</h3>;
};
const Second = () => {
return <h3>Second Page Content</h3>;
};
const Third = () => {
return (
<div>
<div>
<h3>Third Page Content</h3>
<ul>
<li>
<NavLink to="/third-with-items/item1">First Item</NavLink>
</li>
<li>
<NavLink to="/third-with-items/item2">Second Item</NavLink>
</li>
<li>
<NavLink to="/third-with-items/item3">Third Item</NavLink>
</li>
</ul>
</div>
<Redirect to="/third-with-items/item1" />
<Route path="/third-with-items/item1" component={FirstItem} />
<Route path="/third-with-items/item2" component={SecondItem} />
<Route path="/third-with-items/item3" component={ThirdItem} />
</div>
);
};
const FirstItem = () => (
<div>This text should be shown after you click "Third Page with Items"</div>
);
const SecondItem = () => <div>Something...</div>;
const ThirdItem = () => <div>Another something...</div>;
const App = () => {
return (
<div>
<Header />
<Route exact path="/" component={Main} />
<Route path="/first" component={First} />
<Route path="/second" component={Second} />
<Route path="/third-with-items" component={Third} />
</div>
);
}
export default App;

Replace:
<Redirect to="/third-with-items/item1" />
with:
<Route exact path="/third-with-items" component={FirstItem} />
The React Router documentation shows an example of nesting routes that may help you improve your component:
import {BrowserRouter, Route, NavLink, Switch, useRouteMatch} from "react-router-dom";
const Third = () => {
let { path, url } = useRouteMatch();
return (
<div>
<div>
<h3>Third Page Content</h3>
<ul>
<li>
<NavLink to={`${url}/item1`}>First Item</NavLink>
</li>
<li>
<NavLink to={`${url}/item2`}>Second Item</NavLink>
</li>
<li>
<NavLink to={`${url}/item3`}>Third Item</NavLink>
</li>
</ul>
</div>
<Switch>
<Route exact path={path} component={FirstItem} />
<Route path={`${path}/item1`} component={FirstItem} />
<Route path={`${path}/item2`} component={SecondItem} />
<Route path={`${path}/item3`} component={ThirdItem} />
</Switch>
</div>
);
};

Related

React Router: Redirect to a different component

There are two main links on the landing page and then when user clicks on the option it should redirect to that component and not render the component below in the same page.
But, rather than going to the next page, the component is displayed below like a navbar.
const MainRoutes = () => {
return (
<ReactRouterDOM.HashRouter>
<div className='routes'>
<nav>
<ul>
<li>
<Link to='/team-member'>Team Member</Link>
</li>
<li>
<Link to='/moderator'>Moderator</Link>
</li>
</ul>
</nav>
<Route path='/team-member' component={TeamMember} />
<Route path='/moderator' component={Moderator} />
</div>
</ReactRouterDOM.HashRouter>
);
};
const App = () => {
return (
<div className='app'>
<MainRoutes />
</div>
);
};
You need to wrap your routes in Switch:
<Switch>
<Route exact path='/team-member' component={TeamMember} />
<Route exact path='/moderator' component={Moderator} />
</Switch>
As Switch renders the first child <Route> that matches the location. Also, it renders a route exclusively (which you need).
Edit:
If you don't want to show NavBar at other components but only at index route /, you can do this:
const MainRoutes = () => {
return (
<HashRouter>
<div className="routes">
<Switch>
<Route exact path="/" component={NavBar} />
<Route exact path="/team-member" component={TeamMember} />
<Route exact path="/moderator" component={Moderator} />
</Switch>
</div>
</HashRouter>
)
}
NavBar:
function NavBar() {
return (
<nav>
<ul>
<li>
<Link to="/team-member">Team Member</Link>
</li>
<li>
<Link to="/moderator">Moderator</Link>
</li>
</ul>
</nav>
)
}

React routes nesting on homepage

I am able to get routes working on pages level, also nested routes working fine if not on the homepage.
e.g
/ ........ homepage
/pageA ... PageA
/pageA1.....PageA1 (nested)
/PageA2.....PageA2 (nested)
/PageB ... PageB
However, I am unable to have nested routes on homepage, the strucutre looks like below
/..........PageA(homepage)
/pageA1.....PageA1 (nested)
/PageA2.....PageA2 (nested)
/PageB ... PageB
Here is the code, the code can be played on codesandbox
const PageB1 = () => <>"Page B 1"</>;
const PageB2 = () => <>"Page B 2"</>;
const PageB3 = () => <>"Page B 3"</>;
const PageA = ({ match }) => {
return (
<>
<ul>
<li>
<Link to="/pagea1">Page A 1</Link>
</li>
<li>
<Link to="/pagea2">Page A 2</Link>
</li>
<li>
<Link to="/pagea3">Page A 3</Link>
</li>
</ul>
<hr />
<Route exact path={match.path + "/pagea1"} component={PageB1} />
<Route exact path={match.path + "/pagea2"} component={PageB2} />
<Route exact path={match.path + "/pagea3"} component={PageB3} />
</>
);
};
const PageB = () => " Page A";
export default function App() {
return (
<div className="App">
<Router>
<div>
<ul>
<li>
<Link to="/">Page A</Link>
</li>
<li>
<Link to="/pageb">Page B</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/" component={PageA} />
<Route path="/pageb" component={PageB} />
</Switch>
</div>
</Router>
</div>
);
}
This code is working fine for pageA(homepage) and pageB, but nested page Page A 1, Page A 2, Page A 3 are not working
If I remove exact from line 45 change it to <Route path="/" component={PageA} />
and remove match.path + from line 22-24 change the code to
<Route exact path={"/pagea1"} component={PageB1} />
<Route exact path={"/pagea2"} component={PageB2} />
<Route exact path={"/pagea3"} component={PageB3} />
Then the nested pages will work fine. But in the meantime, it breaks PageA(homepage), PageB.
Could someone please to advise where is the error I made? Thanks
Because the props path which you are passing to A component have value "/", and you are adding that value in the nested Route path i.e. //pagea1. so this is the reason it is not working.
one more thing i observed over here it is always suggested to add generic route in the last and here in your code you are add generic "/" route first.
follow the below code :-
const PageB1 = () => <>"Page B 1"</>;
const PageB2 = () => <>"Page B 2"</>;
const PageB3 = () => <>"Page B 3"</>;
const PageA = ({ match }) => {
return (
<>
<ul>
<li>
<Link to="/pagea1">Page A 1</Link>
</li>
<li>
<Link to="/pagea2">Page A 2</Link>
</li>
<li>
<Link to="/pagea3">Page A 3</Link>
</li>
</ul>
<hr />
/* match.path have "/" so you can remove it here
* because you are adding / in pagea1 */
<Route exact path={ "/pagea1"} component={PageB1} />
/* this syntax will work too because i have omit / from pageea2 value */
<Route exact path={match.path + "pagea2"} component={PageB2} />
<Route exact path={match.path + "pagea3"} component={PageB3} />
</>
);
};
const PageB = () => " Page A";
export default function App() {
return (
<div className="App">
<Router>
<div>
<ul>
<li>
<Link to="/">Page A</Link>
</li>
<li>
<Link to="/pageb">Page B</Link>
</li>
</ul>
<hr />
<Switch>
<Route path="/pageb" component={PageB} /> // keep specific paths
//route on top to generic
one
<Route path="/" component={PageA} /> // and in case of generic
you don't need exact
</Switch>
</div>
</Router>
</div>
);}
Hope it will work .
In this You code
exact keyword only render pagea for / url
when we click pagea1, now the url is /pagea1
so we don't see either pagea nor pageb
when you removed exact now every url start with / render pagea, in this case pageb won't work and /pagea1 url also stop works, because you are mentioning
path={match.path + "/pagea1"}
where math.path is / so //pagea1 will render pagea1.
Instead of removing exact and math.path you can create /pagea and redirect / to /pagea
Note: you have to make significant changes in PageA links
After that code will look like this
import { BrowserRouter as Router, Redirect, Route, Switch, Link } from "react-router-dom";
const PageB1 = () => <>"Page B 1"</>;
const PageB2 = () => <>"Page B 2"</>;
const PageB3 = () => <>"Page B 3"</>;
const PageA = ({ match }) => {
return (
<>
<ul>
<li>
<Link to="/pagea/pagea1">Page A 1</Link>
</li>
<li>
<Link to="/pagea/pagea2">Page A 2</Link>
</li>
<li>
<Link to="/pagea/pagea3">Page A 3</Link>
</li>
</ul>
<hr />
<Route exact path={match.path + "/pagea1"} component={PageB1} />
<Route exact path={match.path + "/pagea2"} component={PageB2} />
<Route exact path={match.path + "/pagea3"} component={PageB3} />
</>
);
};
const PageB = () => " Page B";
export default function App() {
return (
<div className="App">
<Router>
<div>
<ul>
<li>
<Link to="/">Page A</Link>
</li>
<li>
<Link to="/pageb">Page B</Link>
</li>
</ul>
<hr />
<Switch>
<Route path="/pagea" component={PageA} />
<Route path="/pageb" component={PageB} />
<Redirect to="/pagea" />
</Switch>
</div>
</Router>
</div>
);
}
PS: Hope I answered your question, if not let me know.

how to get route param values of child component from parent component in app using React Router

if I have nested routes, /:foo/:bar, how would I get access to match.params.bar from the component rendering /:foo? Is context the most common way to do this or...? Thanks.
Don't mind this paragraph. It's just some filler to fulfill the arbitrarily set quota fjewoifjoai;fiwajfoijaweoifjiowfjoiawjfioawjfwafaweffa
I have an example here as well:
const Header = withRouter(({ match }) => {
console.log("match>>>", match.params); // what's the most common way for this to have
// the 'childest' param value?
return (
<header>
<nav id="nav" role="navigation">
<ul>
<li>
<NavLink to="/">Home</NavLink>
</li>
<li>
<NavLink to="/first">First</NavLink>
</li>
<li>
<NavLink to="/second">Second</NavLink>
</li>
<ul>
<li>
<NavLink to="/second/1">Second -> 1</NavLink>
</li>
<li>
<NavLink to="/second/2">Second -> 2</NavLink>
</li>
</ul>
</ul>
</nav>
</header>
);
});
const HomePage = () => {
return <h1>HomePage</h1>;
};
const NumPage = props => {
const num = props.match.params.no;
console.log("num>>", props.match.params, props.match.path);
if (num === "second") {
return (
<div>
<h1>{num}</h1>
<Switch>
<Route path={`${props.match.path}/:sub`} component={SubPage} />
</Switch>
</div>
);
}
return <h1>{num}</h1>;
};
const SubPage = props => {
return (
<h1>
{props.match.params.no} --> {props.match.params.sub}
</h1>
);
};
const App = () => (
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/:no" component={NumPage} />
</Switch>
</div>
</BrowserRouter>
);
You can extend your first Route to check for both the foo as well as the bar parameter like this:
<Route path="/:foo/:bar?" component={NumPage} />
Now your match.params object contains a foo and bar field with the respective values.
You can access these within your child components easily and you dont have duplicated logic with nested routes. By adding the ?, bar becomes optional so that the route "/:foo" is still valid.
Hope this helps. Happy coding.

React throws Warnings when nesting NavLink

I am trying to use NavLink to build a navigation menu and I am using NavLink to implement it because of the activeClassName property.
But when I try to nest them together, to make a Drop Down Menu, warnings show up at Chrome Debugger.
Warning: validateDOMNesting(...): <a> cannot appear as a descendant of <a>.
Is there any way to remove such warnings during development, or is there any better approach for creating NavBar while keeping activeClassName style?
Thanks.
<NavLink to="/dashboard" activeClassName="Activated">
<div className="dropdown">
<label>Items</label>
<div className="dropdown-content">
<NavLink exact to="/dashboard/Item1" activeClassName="Activated">
Item1
</NavLink>
<NavLink exact to="/dashboard/Item2" activeClassName="Activated">
Item2
</NavLink>
<NavLink exact to="/dashboard/Item3" activeClassName="Activated">
Item3
</NavLink>
</div>
</div>
</NavLink>
Edited on 2019-02-20
I have created a CodePen based on the answer from #JupiterAmy, but did not see what expected, can you(or someone) do some modification?
CodePen Link
Navlink components returns an anchor tag itself. You can see that yourself in devtools. That's the reason you are getting this error. For nested links, you should take advantage of react router dynamic routing which was introduced in v4.
To achieve this, you should have a parent component which will have the Navlink which contains the link to your Homepage. something like this.
const Parent = () => {
return (
<div>
<NavLink
to="/dashboard"
activeClassName="Activated">
GoToDashboard
</NavLink>
</div>
);
}
Now in your dashboard component you should have your nested links.
const Dashboard = () => {
return (
<div className="dropdown-content">
<NavLink exact to=`${match.url}/item1` activeClassName="Activated">
Item1
</NavLink>
<NavLink exact to=`${match.url}/item2` activeClassName="Activated">
Item2
</NavLink>
<NavLink exact to=`${match.url}/item3` activeClassName="Activated">
Item3
</NavLink>
</div>
);
}
Now, when you click this Links from inside of Dashboard component, you will get the links as expected.
/dashboard/item..
Check out the below code and image for your reference.
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Switch, Route, NavLink } from 'react-router-dom';
const Root = () => {
return (
<BrowserRouter>
<Parent />
</BrowserRouter>
);
};
const Parent = () => {
return (
<div>
<NavLink className="dropdown"
to="/dashboard"
activeClassName="Activated">
GoToDashboard
</NavLink>
<Route path="/dashboard" component={Dashboard} />
</div>
);
}
const Dashboard = () => {
return (
<div className="dropdown-content">
<NavLink to="/dashboard/item1" activeClassName="Activated">
Item1
</NavLink>
<NavLink to="/dashboard/item2" activeClassName="Activated">
Item2
</NavLink>
<NavLink to="/dashboard/item3" activeClassName="Activated">
Item3
</NavLink>
<Route path="/dashboard/item1" component={Item1} />
<Route path="/dashboard/item2" component={Item2} />
<Route path="/dashboard/item3" component={Item3} />
</div>
);
}
const Item1 = () => {
return (
<div>
<h1>Item1</h1>
</div>
);
}
const Item2 = () => {
return (
<div>
<h1>Item2</h1>
</div>
);
}
const Item3 = () => {
return (
<div>
<h1>Item3</h1>
</div>
);
}
ReactDOM.render(<Root />, document.getElementById('app'));
For some reason, I wasn't able to get the same run in a codepen. You can try to run the code in your local set up. It works and I hope serves your purpose.

React - How to prevent <a href> from refreshing

I have an that links to another stateless component.
I have an onClick listener that calls a method that calls e.preventDefault(), but this just makes the not link to anywhere when clicked.
constructor(props, context) {
super(props, context);
this.preventRefresh = this.preventRefresh.bind(this);
}
<a href={/components/Button'} onClick={this.preventRefresh}>{n.componentName}</a>
preventRefresh(e) {
e.preventDefault();
}
So clicking on the does nothinh. How can I prevent the page from reloading?
In React, this doesn't works:
<a href={/components/Button'} onClick={this.preventRefresh}>{n.componentName}</a>
You can't set the href attribute to a component (a component is not a URL)
If you want to make a navigation link, you should use react-router-dom (if you are working for browsers):
Fist, you have to install it:
npm install --save react-router-dom
Then you can use it, check the official example:
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
const BasicExample = () => (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
</ul>
<hr />
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/topics" component={Topics} />
</div>
</Router>
);
const Home = () => (
<div>
<h2>Home</h2>
</div>
);
const About = () => (
<div>
<h2>About</h2>
</div>
);
const Topics = ({ match }) => (
<div>
<h2>Topics</h2>
<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>
);
const Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
</div>
);
export default BasicExample;
And check the docs here

Resources