react-router-dom v6 NavLink is always active - react-router

I followed Upgrading from v5 guide and I cannot get the NavLink component to work correctly.
https://reactrouter.com/docs/en/v6/upgrading/v5#upgrading-from-v5
v6 Navlinks:
<NavLink
className={(isActive) =>
cx(isActive ? classes.linkActive : classes.link)
}
to="/seafarers"
end
>
Seafarers
</NavLink>
<NavLink
className={(isActive) =>
cx(isActive ? classes.linkActive : classes.link)
}
end
to="/"
>
Planning
</NavLink>
Routes
<BrowserRouter>
<Routes>
<Route path="/" element={<LoginScreen />} />
<Route path="login" element={<LoginScreen />} />
<Route path="forgot-password" element={<ForgotPasswordScreen />} />
<Route path="seafarers" element={<SeafarersScreen />} />
</Routes>
</BrowserRouter>
Both "/" and "/seafarers" have active class
Note: NavLink elements are located in SeafarersScreen screen
How can I correct this issue?

Turns out I had to deconstruct the property of className as ternary operator always returned true for objects
<NavLink
className={({isActive}) => //(isActive) --> ({isActive})
cx(isActive ? classes.linkActive : classes.link)
}
to="/seafarers"
end
>
Seafarers
</NavLink>

For react-router-dom v6
This example demonstrates how to make a custom <Link> component to render something different when the link is "active" using the useMatch() and useResolvedPath() hooks.
Official doc for active link

Add end prop on parent route. This work for react-router-dom: 6.4.4.
<NavLink
to="/"
end
className={({ isActive }) =>`nav-link ${isActive && 'active'}`}>
Notes
</NavLink>
From react-router documentation, here is the link: https://reactrouter.com/en/main/components/nav-link

<NavLink
to="/"
style={({ isActive }) => ({ color: isActive ? "green" : "blue" })}
className={yourClasses}
>
Profile
</NavLink>

Related

How to specify relative "Link to" so that navigation remains within nested route paths?

Ref: https://stackblitz.com/edit/react-ts-7qabya?file=App.tsx
I am trying to create nested routes based on language param in the path URL
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/en">
<Route index element={<Page text="Home Page!" />} />
<Route path="first" element={<Page text="First Page!" />} />
<Route path="second" element={<Page text="Second Page!" />} />
</Route>
<Route
path="*"
element={
<Page
text={"To begin, don't click anything. Change the path to /en"}
/>
}
/>
</Routes>
</BrowserRouter>
);
}
I have trouble figuring out how to specify the "Link to={???}" such that it navigates relative to the language param.
In the below example,
when I click Home, I expect it to stay on domain.com/en but actually it goes to domain.com
when I click first, then second, I expect the path to be domain.com/en/second but actually the path becomes domain.com/en/first/second
const Navigation = () => {
return (
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="first">First</Link>
</li>
<li>
<Link to="second">Second</Link>
</li>
</ul>
);
};
What am I missing here? Thanks.
You are rendering the Navigation component on a sibling route to that where you want the links to navigate to. For this you'd need to specify relative paths into the sibling subroutes.
Example:
const Navigation = () => {
return (
<ul>
<li>
<Link to="../en">Home</Link>
</li>
<li>
<Link to="../en/first">First</Link>
</li>
<li>
<Link to="../en/second">Second</Link>
</li>
</ul>
);
};
I'm going to guess that Navigation was/is supposed to be rendered as part of a layout route for the "/en" sub-routes. You can use "." to refer to the current sub-directory.
Example:
const Layout = () => {
return (
<div>
<Navigation />
<Outlet />
</div>
);
};
const Navigation = () => {
return (
<ul>
<li>
<Link to=".">Home</Link>
</li>
<li>
<Link to="./first">First</Link>
</li>
<li>
<Link to="./second">Second</Link>
</li>
</ul>
);
};
...
<BrowserRouter>
<Routes>
<Route path="/en" element={<Layout />}> // <-- nav from here
<Route index element={<Page text="Home Page!" />} />
<Route path="first" element={<Page text="First Page!" />} />
<Route path="second" element={<Page text="Second Page!" />} />
</Route>
...
</Routes>
</BrowserRouter>

How to use routes starting with same string

Is there any way how to create two different pages starting with same sring - GuidePage and GuideDetailPage using react router?
My code doesn't work, after open /guide it shows GuidePage components, it's ok. But after open /guide/detail it shows GuidePage NOT GuideDetailPage.
What is wrong?
<Router history={history}>
<main>
<MenuHeader />
<Switch>
<Route path='/guide'>
<Route path='/' component={GuidePage} />
<Route path='/detail' component={GuideDetailPage} />
</Route>
<Redirect to='/home' />
</Switch>
<Footer />
</main>
</Router>
So I can use /guide-detail for GuideDetailPage but I want to use guide/detail.
In React Router 4, routes are dynamic instead of static, you don't need to declare all routes in one file, but declare nested route in component having those routes.
Here is Basic Example, You need to add two routes inside Guide Component and to make it relative use match.path.
import React from "react";
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
function BasicExample() {
return (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/guide">Guide</Link>
</li>
</ul>
<hr />
<Switch>
<Route exact path="/" component={Home} />
<Route path="/guide" component={Guide} />
</Switch>
</div>
</Router>
);
}
function Guide({ match }) {
return (
<div>
<h2>Guide</h2>
<ul>
<li>
<Link to={`${match.url}`}>guide</Link>
</li>
<li>
<Link to={`${match.url}/detail`}>Guide Detail</Link>
</li>
</ul>
<Route path={`${match.path}/detail`}
component={GuidePage} />
<Route
exact
path={match.path}
render={() => <h3>Please select a topic.</h3>}
/>
</div>
);
}
function Home() {
return (
<div>
<h2>Home</h2>
</div>
);
}
function GuidePage() {
return (
<div>
<h2>GuidePage</h2>
</div>
);
}
export default BasicExample;
Hope that helps!!!

prevent react router transitions on parent route when moving between child routes

I have set up a router with animating transitions. Is it possible to trigger transitions on child routes only rather than transitioning the whole page when the user moves from /topics/rendering to /topics/components. Live example: https://codesandbox.io/s/r0PvB30wk
import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom'
import { CSSTransitionGroup } from 'react-transition-group'
import About from './components/About'
import Home from './components/Home'
import Topics from './components/Topics'
import './styles.css'
const Topic = ({ match }) => (
<div>
<h3>{match.params.topicId}</h3>
</div>
);
const Topics = ({ match }) => (
<div className="page">
<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>
);
export default Topics;
const BasicExample = () => (
<Router>
<Route render={({ location, history, match }) => (
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/topics">Topics</Link></li>
</ul>
<hr />
<CSSTransitionGroup
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
transitionName="fade"
>
<Switch key={location.key} location={location}>
<Route exact path="/" component={Home} location={location} key={location.key} />
<Route path="/about" component={About} location={location} key={location.key} />
<Route path="/topics" component={Topics} location={location} key={location.key} />
</Switch>
</CSSTransitionGroup>
</div>
)}/>
</Router>
)
render(<BasicExample />, document.body)
The only way I could achieve the result was to detect if I was changing from parent to parent or child to child route and turn off the transition based on that.
import React, { Component } from 'react';
import { render } from 'react-dom';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import { CSSTransitionGroup } from 'react-transition-group';
import About from './components/About';
import Home from './components/Home';
import Topics from './components/Topics';
import './styles.css';
var currentRoute = ''
const getParentPathname = pathname =>
pathname === '/'
? ''
: (/([a-zA-Z])([^/]*)/).exec(pathname)[0]
class BasicExample extends Component {
render = () =>
<Router>
<Route
render={({ location, history, match }) => {
const nextRoute = getParentPathname(location.pathname)
const isAnimated =
!currentRoute.includes(nextRoute) ||
!nextRoute.includes(currentRoute)
currentRoute = nextRoute
return(
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/topics">Topics</Link>
</li>
<li>
<Link to="/askdjhakshd">Unknown</Link>
</li>
</ul>
<hr />
<CSSTransitionGroup
transitionEnter={isAnimated}
transitionLeave={isAnimated}
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
transitionName="fade"
>
{/* switch will render the 1st route that matches */}
<Switch key={location.key} location={location}>
<Route exact path="/" component={Home} location={location} key={location.key}/>
<Route path="/about" component={About} location={location} key={location.key}/>
<Route path="/topics" component={Topics} location={location} key={location.key}/>
<Route render={props => <p>Unknown Route</p>}/>
</Switch>
</CSSTransitionGroup>
</div>
)
}
}/>
</Router>
}
render(<BasicExample />, document.body)
I headed the same problem, so I wrote the module solving this.
https://github.com/melounek/switch-css-transition-group
This SwitchCSSTransitionGroup component is detecting if routes inside are really switching (based on react-router.matchPath) and then changing the Switch key approprietly.
So you can update this code-snippet and it should work:
...
<SwitchCSSTransitionGroup
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
transitionName="fade"
location={location}>
<Route exact path="/" component={Home} location={location} key={location.key} />
<Route path="/about" component={About} location={location} key={location.key} />
<Route path="/topics" component={Topics} location={location} key={location.key} />
</SwitchCSSTransitionGroup>
...

React-Redux Routing TypeError: this.props.match is undefined

I am new to routing and have been trying around to see what I can do with it.
I want to be able to pass params to a nested route/component. I can pass params to the first component SurveyList. But in the Survey component the params remain undefined.
Is there a way to do this?
routes:
<Switch>
<Route path="/" exact component={Home} />
<Route path="/aboutus/:username" component={AboutUs} />
<Route path="/survey/" component={SurveyList} >
<Route path="/page/:id/" component={Survey} />
</Route>
<Route component={NotFound} />
</Switch>
survey-list
eachSurvey(newText, i){
return(
<Survey key={i} index={i}>
{newText.title}
{newText.desc}
</Survey>);
}
renderList() {
return (
<li>
{this.props.surveys.map(this.eachSurvey)}
</li>
);
}
render() {
return (
<div id="newSurveyButton">
<RaisedButton containerElement={<Link to="survey/page/1"/>} label="Add new Survey" onClick={() => this.props.addSurvey("Titel Survey", "Survey beschrijving")}></RaisedButton>
<ul>
{this.renderList()}
</ul>
</div>
);
}
Survey component render:
<div className="surveyContainer">
<p>{this.props.match.params.id}</p>
<div className="surveyTitle">{this.props.children[0]}</div>
<div className="surveyDesc">{this.props.children[1]}</div>
<RaisedButton label="Add new Page" onClick={() => this.props.addPage("title Page", "Page desc")}></RaisedButton>
<button onClick={this.edit} className="button-primary">Edit</button>
<RaisedButton label="Show Page" onClick={() => this.selectPage(0)} className="showPage"></RaisedButton>
{this.renderPage()

In react-router v4, can we still do `this.props.children` in the App container?

In react-router v2, we can do
// inside of routes.js
export default (
<Route path="/" component={App}>
<IndexRoute component={PostsIndex} />
<Route path="/posts/new" component={PostsNew} />
<Route path="/posts/:id" component={PostsShow} />
</Route>
);
and then show the proper child inside of the App container:
// inside app.js
{this.props.children}
but in react-router v4, {this.props.children} doesn't work any more. Is it done another way?
V4 is a major update and is a complete re-write of <=V3. You shouldn't expect to upgrade to V4 and have everything work still. I recommend checking out the documentation examples so you can see how things work. Here's a basic example which demonstrates nested routes like you have.
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
I had the same problem and it took a few hours until i found the solution
const PageNavWithChildrenComponentInsideLayout = ({ match }) => {
return <div>
<ul>
<li>
<NavLink className="nav-link" to={"/consultant/childOne"} activeClassName={`${match.url}/childOne` ? "active": ""}>
</NavLink>
<NavLink className="nav-link" to={"/consultant/childTwo"} activeClassName={`${match.url}/childTwo` ? "active": ""}>
</NavLink>
<NavLink className="nav-link" to={"/consultant/childThree"} activeClassName={`${match.url}/childThree` ? "active": ""}>
</NavLink>
</li>
</ul>
{/**
Layout is Wrapper component corresponds parent v2,v3
**/}
<Layout>
{/**corresponds {this.props.children}**/}
<Route exact path={`${match.url}/childOne`} component={ChildOne}/>
<Route exact path={`${match.url}/childTwo`} component={ChildTwo}/>
<Route exact path={`${match.url}/childThree`} component={ChildThree}/>
</Layout>
</div>;
};
export default PageNavWithChildrenComponentInsideLayout
{/**-------------Routes.js--------------*/}
export default routes=()=>{
<Switch>
<Route path="/PageNavWithChildrenComponentInsideLayout" name="Parent" component={PageNavWithChildrenComponentInside}>
<Route path="/PageNavWithChildrenComponentInsideLayout/childOne" name="ChildOne" component={ChildOne} />
<Route path="/PageNavWithChildrenComponentInsideLayout/childTwo" name="ChildTwo" component={ChildTwo} />
<Route path="/PageNavWithChildrenComponentInsideLayout/childThree" name="ChildThree" component={ChildThree} />
</Route>
</Switch>
}