Link, activeClassName just plain not working - reactjs

I cannot get activeClassName to render correctly in my Navigation component Link. This has been tracked down to the following symptoms:
In the following code, the Navigation component is not getting a props.route passed to it at all. The App component has a props.route, however it is never updated as the user navigates to other routes. It is always set to the first route that was loaded. The componentWillReceiveProps is fired when changing routes, as the props.children is changing.
Here are the relevant snippets of my files:
app.jsx
import router from 'app/router';
[...]
ReactDOM.render(
<Provider store={store}>
{router}
</Provider>,
document.getElementById('app')
);
router/index.jsx
export default (
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={GameBoard}/>
<Route path="profile" component={ProfileBoard}/>
<Route path="profile/:userId" component={ProfileBoard}/>
<Route path="help" component={Help}/>
<Route path="about" component={About}/>
</Route>
</Router>
);
App.jsx
import React from 'react';
import Navigation from 'Navigation';
export default class App extends React.Component {
static propTypes = {};
constructor(props) {
super(props);
}
render() {
return (
<div>
<Navigation />
<div className="content">
{this.props.children}
</div>
</div>
);
}
};
Navigation.jsx
import React from 'react';
import {connect} from 'react-redux';
import {IndexLink, Link} from 'react-router';
export class Navigation extends React.Component {
static propTypes = {};
constructor(props) {
super(props);
}
render() {
return (
<div className="top-bar navigation">
<div className="row">
<div className="small-12 columns">
<div className="top-bar-left">
<ul className="menu">
<li className="menu-text">
TVDeadpool.xyz
</li>
<li>
<IndexLink to="/" activeClassName="link-active">Bets</IndexLink>
</li>
<li>
<Link to="/help" activeClassName="link-active">Help</Link>
</li>
<li>
<Link to="/about" activeClassName="link-active">About</Link>
</li>
</ul>
</div>
</div>
</div>
</div>
);
}
}
export default connect()(Navigation);
Navigation.jsx has been simplified. I removed some functionality that shows a Logout link if you are logged in, and handles that link. That is the reason I am including connect, though.
I am pouring through the documentation for react-router but cannot for the life of me figure out where I am going wrong. It must be something to do with nesting within the <Provider/>, I guess? Any help would be appreciated!
Note that if you want to see this in (in)action, check out TVDeadpool.xyz. Not a plug, just a fact.
UPDATE
Here is a hack fix:
App.jsx
import React from 'react';
import Navigation from 'Navigation';
export default class App extends React.Component {
static propTypes = {};
constructor(props) {
super(props);
}
render() {
return (
<div>
<Navigation location={this.props.location.pathname}/>
<div className="content">
{this.props.children}
</div>
</div>
);
}
};
Simply adding that location prop to <Navigation/> causes a re-render, without any additional code.
I think the reason this is happening is that App is always considered to be at route.path of "/", no matter what route is actually showing. Its immediate children seem to get the appropriate route.path, but Navigation, being a nested component of App, does not. In fact, it does not receive a route prop at all because it is not directly referenced by a <Route/>.
That said, how would this ever work? Should you not be able to simple include Link and expect it to work as described? I feel like I am missing something key to how react-router is supposed to work.

I think the reason activeClassName is not working is because you are using connect
export default connect()(Navigation);
See this issue... https://github.com/reactjs/react-redux/issues/388
It is reported as fixed in React Router 3.0.0-alpha.1 and newer.
Another hack fix I found you can use in older versions of react router is to pass {pure : false} to tell connect it is not a pure component...
export default connect(mapStateToProps, null, null, { pure: false })(Navigation);

Related

React - state on multiple pages

I would like to set up a portfolio page in react which would have different pages (CV, News, Works), and they would be different components.
What bothers is me though that setting up like this, I cannot do an "App.js" as "mama-component" because all the components are in different url and they have at least one different component inside them. For example my Curriculum component:
import React from 'react';
import Header from './Header';
class Curriculum extends React.Component {
render() {
return (
<React.Fragment>
<Header />
<article className="curriculum">
<div className="curriculum-text">
<ul>
<li>
2022
<ul>
<li>Participating in ..........</li>
<li>Released my co-authored article "Three Ways of .....</li>
</ul>
</li>
<li>
2021
<ul>
<li>
Starting to work at the ..........
<ul>
<li>Working on the ......</li>
<li>Assisting to ......</li>
<li>Reviewing and drafting contracts</li>
</ul>
</li>
<li>Participating IRIS conference with my paper titled "...." ......</li>
</ul>
</li>
<li>
2020
<li></li>
</li>
</ul>
</div>
</article>
</React.Fragment>
);
}
}
export default Curriculum;
My News component:
import React, { Fragment } from 'react';
import Header from './Header';
class News extends React.Component {
goToNews = (event) => {
event.preventDefault();
const currentNews = event.target.parentElement.id;
this.props.history.push(`/news/${currentNews}`);
console.log(this.props);
};
render() {
return (
<React.Fragment>
<Header />
<article className="news">
<div className="news-text">
<h2>News from the World </h2>
<div id="news1" value="news1" onClick={this.goToNews}>
<h3>News1</h3>
</div>
<div id="news2" value="news2" onClick={this.goToNews}>
<h3>News2</h3>
</div>
<div id="news3" value="news3" onClick={this.goToNews}>
<h3>News3</h3>
</div>
</div>
</article>
</React.Fragment>
);
}
}
export default News;
I tried to convert my Router.js to an App component as this:
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Introduction from './Introduction';
import Curriculum from './Curriculum';
import Works from './Works';
import News from './News';
import CurrentNews from './CurrentNews';
import NotFound from './NotFound';
class App extends React.Component {
yellAtMe = (fish) => {
console.log('heeeeeeeeeeeeeeeeeee');
};
render() {
return (
<Router yellAtMe={this.yellAtMe}>
<Switch yellAtMe={this.yellAtMe}>
<Route exact path="/" component={Introduction} />
<Route exact path="/curriculum" component={Curriculum} />
<Route yellAtMe={this.yellAtMe} exact path="/works" component={Works} />
<Route exact path="/news" component={News} />
<Route path="/news/:newsId" component={CurrentNews} />
<Route component={NotFound} />
</Switch>
</Router>
);
}
}
export default App;
However I cannot pass through Route, Switch and Router props to the components (at least I did not succeed).
My questions are:
am i allowed to set up more states for each component? (that would contradict to Wes Boss's advice according which the state should be a "single source of truth")
is there any way to pass props through the Router components? (or do you think my solution to convert it to an App.js is adequate)
Should I somehow put all my components in one big component? (so somehow it would result like that:) - but then how to make them each as a different page with different url?)
<App />
<Header />
<Always-change-component />
<CV />
<News />
<Works />
Thanks in advance!
"am i allowed to set up more states for each component? (that would contradict to Wes Boss's advice according which the state should be a "single source of truth")" - you can create states for each component, but in that state you should keep only internal information that is related and used in this component and all common information you should pass through props from parent component.
"is there any way to pass props through the Router components? (or do you think my solution to convert it to an App.js is adequate)" - you should keep shared information in parent component, just rewrite Routes structure like this:
<Router>
<Switch>
<Route exact path="/">
<Introduction yellAtMe={this.yellAtMe}/>
<Route />
....
</Switch>
</Router>
Should I somehow put all my components in one big component? (so somehow it would result like that:) - but then how to make them each as a different page with different url?) - answer is above.

React-router URL changes but page is still unchanged

I am new to react and react-router, so please go easy on me.
I am trying to implement router in my Todo List project, where path="/" takes me to my todo list and path="/id" takes me to a test page (later will show the description of the task).
When I click the link that takes me to "/id", the URL in the browser changes but the page/content doesn't. However, when I refresh my browser, the test page loads.
I have put the Switch in App.js shown below.
import React, { Component } from "react";
import "./App.css";
import TodoList from "./components/TodoList";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Test from "./components/Test";
class App extends Component {
render() {
return (
<Router>
<div className="todo-app">
<p>
<Link to="/">Home</Link>
</p>
<Switch>
<Route exact path="/" component={TodoList} />
<Route path={`/id`} component={Test} />
</Switch>
</div>
</Router>
);
}
}
export default App;
And I have put the Link to "/id" as shown below in a child component of component which is called here in App.js.
<div key={todo.id}>
<Link className="todo-text" to={`/id/${todo.id}`}>
{todo.text}
</Link>
</div>
Am I missing something which is causing my component to not load when I click the link?
Edit: Here's a link to my project. https://stackblitz.com/edit/react-7cpjp9?file=src/index.js
Issue
Ok, the issue is exactly as I had suspected. You are rendering multiple routers in your app. The first is a BrowserRouter in your index.js file, the second, another BrowserRouter in App.js, and at least a third BrowserRouter in Todo.js. You need only one router to provide a routing context for the entire app.
The issue here is that the router in Todo component is the closest router context to the links to specific todo details. When a link in Todo is clicked, this closest router handles the navigation request and updates the URL in the address bar. The blocks, or "masks", the router in App component or index.js that is rendering the routes from "seeing" that a navigation action occurred. In other words, the URL in the address bar is updated by the inner router, but the outer router doesn't know to render a different route.
Solution
Keep the BrowserRouter wrapping App in index.js and remove all other routers used in your app.
App - Remove the Router component. Also, reorder the routes/paths from most specific to least specific so you don't need to specify the exact prop on every route. Allows more specific paths to be matched and rendered before less specific paths by the Switch component.
class App extends Component {
render() {
return (
<div className="todo-app">
<p>
<Link to="/">Home</Link>
</p>
<Switch>
<Route path="/id/:todoId" component={Test} />
<Route path="/" component={TodoList} />
</Switch>
</div>
);
}
}
Todo - Remove the Router component. Move the key={todo.id} up to the outer-most element so when todos array is updated React can reconcile updates.
class Todo extends Component {
constructor(props) {
super(props);
this.state = {
id: null,
value: "",
details: "",
};
this.submitUpdate = this.submitUpdate.bind(this);
}
submitUpdate(value) {
const { updateTodo } = this.props;
updateTodo(this.state.id, value);
this.setState({
id: null,
value: "",
});
}
render() {
const { todos, completeTodo, removeTodo } = this.props;
if (this.state.id) {
return <TodoForm edit={this.state} onSubmit={this.submitUpdate} />;
}
return todos.map((todo, index) => (
<div
className={todo.isComplete ? "todo-row complete" : "todo-row"}
key={todo.id}
>
<div>
<Link className="todo-text" to={`/id/${todo.id}`}>
{todo.text}
</Link>
</div>
<div className="icons">
<RiCloseCircleLine
onClick={() => removeTodo(todo.id)}
className="delete-icon"
/>
<TiEdit
onClick={() => this.setState({ id: todo.id, value: todo.text })}
className="edit-icon"
/>
<RiCheckboxCircleLine
onClick={() => completeTodo(todo.id)}
className="delete-icon"
/>
</div>
</div>
));
}
}
First of all the approach, you are taking for dynamic routing is wrong.
It should be like this you will have to add the exact keyword on the dynamic route.
<Route exact path="/id/:todoId" component={Test} />
And
<div key={todo.id}>
<Link className="todo-text" to={`/id/${todo.id}`}>
{todo.text}
</Link>
import React, { Component } from "react";
import "./App.css";
import TodoList from "./components/TodoList";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Test from "./components/Test";
class App extends Component {
render() {
return (
<Router>
<div className="todo-app">
<p>
<Link to="/">Home</Link>
</p>
<Switch>
<Route exact path="/" component={TodoList} />
**<Route exact path={`/id`} component={Test} />**
</Switch>
</div>
</Router>
);
}
}
export default App;

React Router Switch did not work properly

I am new to react and trying to create simple navigation which has two menu items (Dashboard and Users). But when I click on Users link it did not render that page content, but dashboard content getting hide. Someone please help me to resolve the issue.
App.js
import React, { Component } from 'react';
import Login from './pages/Login';
import { BrowserRouter as Router, Switch, Route, Link, Redirect, withRouter } from 'react-router-dom';
import { history } from './_helpers/history';
import { authenticationService } from './_services/authentication.service';
import Users from './pages/users/Users';
import Dashboard from './pages/Dashboard';
class App extends Component {
constructor(props) {
super(props);
this.state = {
currentUser: null
};
}
componentDidMount() {
authenticationService.currentUser.subscribe(x => this.setState({ currentUser: x }));
}
logout() {
authenticationService.logout();
history.push('/login');
}
render () {
const { currentUser } = this.state;
return (
currentUser ? (
<Router>
<div id="wrapper">
<ul>
<li><Link to={'/'} className="nav-link" > <i className="fas fa-fw fa-tachometer-alt"></i> <span>Dashboard</span> </Link></li>
<li><Link to={'/users'} className="nav-link" > <i className="fas fa-fw fa-users"></i> <span>Users</span> </Link></li>
</ul>
<Switch>
<Route path='/' component={Dashboard} />
<Route path='/users' component={Users} />
</Switch>
</div>
</Router>
) : <Login />
);
}
}
export default App;
Dashboard.js
import React, { Component } from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import { authenticationService } from '../_services/authentication.service';
import { history } from '../_helpers/history';
class Dashboard extends Component {
constructor (props){
super(props);
if (authenticationService.currentUserValue) {
history.push('/');
}
this.state = {
isPage: '/'
}
}
render (){
if(this.state.isPage == window.location.pathname){
return (
<div className="container">
dashboard
</div>
)
}else{
return '';
}
}
}
export default Dashboard;
Users.js
import React, { Component } from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import { authenticationService } from '../../_services/authentication.service';
import { history } from '../../_helpers/history';
class Users extends Component {
constructor (props){
super(props);
if (authenticationService.currentUserValue) {
history.push('/');
}
this.state = {
isPage: '/users'
}
}
render (){
if(this.state.isPage == window.location.pathname){
return (
<div className="container">
users
</div>
)
}else{
return '';
}
}
}
export default Users;
In App.js component; make Switch direct child of Router; that will fix the issue. You can refactor your code like so:
<Router>
<Switch>
<div id="wrapper">
<ul>
<li><Link to={'/'} className="nav-link" > <i className="fas fa-fw fa-tachometer-alt"></i> <span>Dashboard</span> </Link></li>
<li><Link to={'/users'} className="nav-link" > <i className="fas fa-fw fa-users"></i> <span>Users</span> </Link></li>
</ul>
<Route path='/' component={Dashboard} />
<Route path='/users' component={Users} />
</div>
</Switch>
</Router>
but dashboard content getting hide.
Can you elaborate on that? I'm not quite understanding what you mean.
The problem may lie with your use of react lifecycles.
authenticationService.currentUser.subscribe()
is set on componentDidMount() so only after the JSX gets mounted to the DOM. Your Users component is checking authenticationService.currentUserValue on the constructor which runs first before it gets mounted. authenticationService.currentUserValue maybe giving you a falsy which will kick you out to /. Console log that value or place those inside a componentDidMount so it will only check after the mount.
constructor (props){
super(props);
this.state = {
isPage: '/users'
}
}
componentDidMount() {
if (authenticationService.currentUserValue) {
history.push('/');
}
}
When using the <Switch> component, it will render the first component (in order) that matches the path. Optionally you can put an exact prop on the route so it must match the path 100%.
Your <Dashboard> component is being rendered, however your logic for returning an empty string if the path does not match is preventing you from seeing it. You can move the <Users> route higher, or put an exact prop on your routes.
I've created a small CodeSandbox
https://codesandbox.io/s/festive-worker-t7ly3
I assume your ../../_helpers/history looks like that
import { createBrowserHistory } from "history";
export default createBrowserHistory();
You forget to pass history to Router as props, so other components do not know what is history
<Router history={history}>...</Router>`
According to the documentation, <Switch>
Renders the first child or that matches the location.
In your code you have:
<Route path='/' component={Dashboard} />
<Route path='/users' component={Users} />
The problem is that path='/' actually matches any path, including /users, because /users starts with /. So when the route is /users, the Redirect component renders the Dashboard Route and stops looking for other routes.
To fix this, you could add the exact prop to the / Route. exact means that / will not match anything except paths that are exactly "/":
<Route exact path="/" component={Dashboard} />
<Route path="/users" component={Users} />
Now, if the path is /users, the Dashboard Route no longer matches, and the Switch checks if the next Route matches, which it does!
Fixed & simplified example: https://codesandbox.io/s/lucid-leaf-fkgv9
Note that I have removed some code (like this.state.isPage == window.location.pathname) which seemed to be checking if the route matches. You don't need to worry about this in your components, because React-Router takes care of all the routing for you!
Another solution would be to put the Users Route first so that it is checked first, but this can get messy if you have multiple Routes and want to keep them organized.
Remove this line.
if (authenticationService.currentUserValue) {
history.push('/');
}
this is redirecting you again and again to the same page.

not getting location react router?

id like to change my header color when in my contact page. unable to get location from react router. I get "TypeError: Cannot read property 'pathname' of undefined".
I know I'm missing something simple but i cant seem to figure it out.
import React from 'react';
import './Header.css';
import { NavLink } from 'react-router-dom';
class Header extends React.Component {
render() {
const blackHeader = {"background-color" : 'black'}
const clearHeader = {"background-color" : 'transparent'}
return(
<header style={ this.props.location.pathname === '/Contact' ? { blackHeader } : { clearHeader } }>
<NavLink to={'/'}><h2>Name</h2></NavLink>
<nav>
<ul>
<li><NavLink to={'/'}>Portfolio</NavLink></li>
<li><NavLink to={'Contact'}>Contact</NavLink></li>
<li>Blog</li>
</ul>
</nav>
</header>
)
}
}
export default Header;
You need to pass the location props to your Header component or you can use the HOC withRouter in react-router-dom.
import { NavLink, withRouter } from 'react-router-dom';
export default withRouter(Header)
Edit for clarity:
If you render a component in your router in a Route component:
<Route path='/' component={Home} />
This will give that component (Home in this case) access to the location props which can then be passed to children of that component.
import React, { Component } from 'react'
import OtherComponent from './OtherComponent'
class Home extends Component {
render() {
return (
<div>
<OtherComponent locations={this.props.locations}
</div>
)
}
}
export default Home;
However, if the component is not passed through Route it will not have access to location props. For example, if you put your header outside of your routes, something like:
<Router>
<Header/>
<Switch>
<Route path='/' component={Home}/>
</Switch>
</Router>
It will not have location props.
I'm not sure how your component tree is set up but, I hope this gives a little clarity to how the location props are accessed and used in your app.
The documentation is a good place to get some more clarity on the workings of react router as well https://reacttraining.com/react-router/core/api/Route/route-props
Yea, I can see in the contact path
<li><NavLink to={'Contact'}>Contact</NavLink></li> ,
it should be something like
<li><NavLink to='/Contact'>Contact</NavLink></li>.
You have to include the back slash, I mean this ' / ' before the pathname. So try it now.

You should not use Link outside a Router using react-router-dom

I am trying to do routing using react-router-dom. I got an error like you should not use outside the like this.Here below i am attaching my code please check.
index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createLogger } from 'redux-logger'
import App from './containers/App';
render(
<Provider>
<App/>
</Provider>,
document.getElementById('root')
)
App.js
import React from 'react';
import Dropdown from './dropdown';
import './styles.css';
import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom';
import Home from './Home';
import Addatttemp from './Addatttemp';
const options = [
{label: 'One' },
{label: 'Two'},
{label: 'Three'},
{label: 'Four' }
]
const AgentValues=[
{label: 'Add Att/temp', value:'Addatttemp' },
{label: 'Mod prof LVL',value:'Modproflvl'},
{label: 'Override Attr',value:'Overrideattr'},
{label: 'Temp',value:'temp'}
]
const defaultOption = options[0]
export default class App extends React.Component {
constructor (props) {
super(props)
}
render() {
return (
<div>
<div className="navbar">
<div className="align">
<div className="dropdown">
<Dropdown options={AgentValues} name='Agent'/>
</div>
<div className="dropdown">
<Dropdown options={options} name='Templete'/>
</div>
<div className="dropdown">
<Dropdown options={options} name='Report'/>
</div>
<div className="dropdown">
<Dropdown options={options} name='Search'/>
</div>
</div>
</div>
<Router>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/Agent/Addatttemp' component={Addatttemp} />
</Switch>
</Router>
</div>
);
}
}
Dropdown.js
import React, { Component, PropTypes } from "react";
import { Link } from 'react-router-dom';
class dropdown extends Component {
constructor (props) {
super(props)
}
render(){
var dropdownList = []
this.props.options.map((res)=>{
dropdownList.push(<Link to={'/'}>{res.label}</Link>)
})
return(
<div>
<button className="dropbtn"><Link to={'/'}>{this.props.name}</Link>
</button>
<div className="dropdown-content">
{dropdownList}
</div>
</div>
)
}
}
export default dropdown;
Home.js
import React from 'react'
const Home = () => (
<div>
<h1>Welcome to the Tornadoes Website!</h1>
</div>
)
export default Home
Addatttemp.js
import React from 'react'
const AddTemp = () => (
<div>
<h1>Welcome to the Add att/temp!</h1>
</div>
)
export default AddTemp
Like this i wrote my code, but when i run this code it throws me the error like Uncaught Error: You should not use Link outside a Router . I am unable to resolve this, please give me suggestions that what i did wrong in it, Any help much appreciated.
As the error message says you are not having Router up the component tree.
You could use it in App component like this
export default class App extends React.Component {
render() {
return (
<Router>
<div>
<div className="navbar">
<div className="align">
<div className="dropdown">
<Dropdown options={AgentValues} name='Agent'/>
</div>
<div className="dropdown">
<Dropdown options={options} name='Templete'/>
</div>
<div className="dropdown">
<Dropdown options={options} name='Report'/>
</div>
<div className="dropdown">
<Dropdown options={options} name='Search'/>
</div>
</div>
</div>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/Agent/Addatttemp' component={Addatttemp} />
</Switch>
</div>
</Router>
);
}
}
Also
You don't need constructor that just calls super
// this is noop
constructor (props) {
super(props)
}
You don't need to push to external array when using map.
var dropdownList = this.props.options.map(res => (
<Link to={'/'}>{res.label}</Link>
))
If you are trying to show a Link on a page or any element outside of BrowserRouter you are going to get this error. This error message is saying that any component that is not a child of a router cannot contain any react-router-dom related components.
What you need to learn for effective development in React is your component hierarchy.
In your case your parent component, the one at the top of the hierarchy is App, how do I know? because you said so right here in your index.js file:
render(
<Provider>
<App/>
</Provider>,
document.getElementById('root')
)
So, when working with react-router-dom, next up on that hierarchy chart should be your BrowserRouter. This is where you start to lose me and React as we are both looking for BrowserRouter inside your App component and do not see it.
This is what we should be seeing right now:
const App = () => {
return (
<div className="ui container">
<BrowserRouter>
<div>
<Route path="/" exact component={Home} />
<Route path="/Agent/Addatttemp" exact component={Addattemp} />
</div>
</BrowserRouter>
</div>
);
};
Notice I am using the exact keyword? I am using the exact keyword so I don’t accidentally match one of these other routes I am about to implement.
So please go ahead and add your BrowserRouter or in your case, your Router which is acting as BrowserRouter higher up on that hierarchy below App, it has to be next up in the hierarchy, everything else is a child of that BrowserRouter and must go within it.
And since you are never going to make use of the Link from react-router-dom inside your App component, go ahead and clean it up like so:
import { BrowserRouter, Route } from "react-router-dom";
Once you follow the above, that error should go away.

Resources