Get an active route with react-router in ReactJs - javascript

I am creating a project using reactjs.In my application i am getting the active route using this:
this.context.router.isActive
but getting undefined, i am using the react-router 4.Earlier this was worked for me when am using the lower version of react-router.
Here is my code:
class NavLink extends React.Component {
render() {
console.log( this.context.router.isActive)
let isActive = this.context.router.isActive(this.props.to, true);
let className = isActive ? "active" : "";
return(
<li className={className} {...this.props}>
<Link {...this.props}/>
</li >
);
}
}
NavLink.contextTypes = {
router: PropTypes.object
};

The architecture has changed in react-router v4 and this.context.router.isActive is no longer supported.
In react-router-v4, you could instead of creating a NavLink yourself use the exposed NavLink component.
From the documentation:
NavLink
A special version of the that will add styling attributes to
the rendered element when it matches the current URL.
import { NavLink } from 'react-router-dom'
<NavLink to="/about">About</NavLink>
It also provides you an activeClassName prop:
activeClassName: string
The class to give the element when it is active. The default given
class is active. This will be joined with the className prop.
<NavLink
to="/faq"
activeClassName="selected"
>FAQs</NavLink>
Why is this.context.router.isActive not supported:
Here is an excerpt from a github issue
Rendering a <Route> does not necessarily mean "only render when you match the current location". For example, it can be used inject the router variables from the context into a component as props.
In <NavLink>, the <Route> is using the children prop, which means that it will call the children function whether or not the route matches. If the match is null, then we know that it did not match.
If you would prefer not to use <Route children> in this way, React Router offers an imperative approach with the matchPath function. This is what <Switch>es and <Route>s use internally to match a location against a path.
If your component is not receiving the Router props, then you could inject it using the withRouter HOC
import { withRouter } from 'react-router';
export class withRouter(MyComponent);

A simple way to style active router
import NavLink
import { NavLink } from 'react-router-dom';
<NavLink className={({ isActive }) => (isActive ? 'active-link' : 'link')} to="/home">Home</NavLink>
in style.css
.active-link {
color: hsl(96, 82%, 35%);
}
customize the style looks more beautiful.
~cheers

Related

React/Router/MemoryRouter - how to pass history property and use push() in child component?

I'm building a React app where I do NOT want the URL in the browser to be updated. I am NOT using 'react-router-dom' but only 'react-router' and MemoryRouter (https://reacttraining.com/react-router/web/api/MemoryRouter). The history.push() is available directly in the component statements but I wish to pass the history to children of children of these main components but the property is undefined.
Here is the Router section in main App.js (components Home and ScreeningTool can access this.props.history.push() as expected):
...
import {Route, MemoryRouter} from "react-router";
...
<MemoryRouter>
<Route exact path="/" component={Home} />
<Route path="/screening-tool" component={ScreeningTool} />
</MemoryRouter>
...
Both Home and ScreeningTool both use child component AppLink that generates a 'link' to navigate between Home and ScreeningTool like so (notice I'm passing 'history' as a prop):
Home.js:
...
<AppLink
url="/"
label="Screening Tool"
history={this.props.history}
/>
...
AppLink.js:
...
<div className="pseudo-link">
<span onClick={() => this.props.history.push(this.props.url)}>
{this.props.label}
</span>
</div>
...
The above code works. But there are children components in Home that will create their own AppLinks and also greatgrandchildren. I do NOT want to pass the history property as a component prop from Parent to Child to GrandChild components because this does not seem efficient. I have found the following stackoverflow questions but none of these options are working for me:
this.props.history.push works in some components and not others
react-router getting this.props.location in child components
I tried the newer 'userHistory' as described in the second URL above:
...
import { useHistory } from 'react-router';
...
render() {
let history = useHistory();
return (
<div className="pseudo-link">
<span onClick={() => history.push(this.props.url)}>
{this.props.label}
</span>
</div>
);
}
...
but I get Error: Invalid hook call. Hooks can only be called inside of the body of a function component..
I tried using withRouter as defined here https://reacttraining.com/react-router/web/api/withRouter but I get Error: Invariant failed: You should not use <withRouter(PseudoLink) /> outside a <Router>.
Finally, the accepted answer for this.props.history.push works in some components and not others ends with block of code export default withRouter(connect(mapStateToProps, matchDispatchToProps)(ChildView)); but does not explain where mapStateToProps or matchDispatchToProps comes from?
I'm thinking the issue is that I am using MemoryRouter and not normal/most common Router from 'reacto-router-dom'.
Can anyone help me out?
useHistory is a Hook so it should be used in a functional component, not inside a class based component.
Finally, the accepted answer for this.props.history.push works in some components and not others ends with block of code export default withRouter(connect(mapStateToProps, matchDispatchToProps)(ChildView)); but does not explain where mapStateToProps or matchDispatchToProps comes from?
-If you're not using redux then you can just use
export default withRouter(yourComponentName);
update
I've changed the AppLink component to this and it is working fine
import React from "react";
import "./AppLink.css";
import { useHistory } from 'react-router';
const AppLink = props => {
const history = useHistory();
return (
<div
className="app-link-button"
onClick={() => history.push(props.url)}
>
<span>{props.label}</span>
</div>
);
};
export default AppLink;

How do I navigate to a different route in my react application?

I want to create new Routes for my works web application, but the way that they are using the Routes is not the way I thought they would be used. Essentially I want to add new routes and be able to click on say like a button, and that will take me to the desired route that I want to go to.
The way it is set up in our application is that you actually have to manually go in the browser and type in the extended path to access that route, which doesn't seem like a good way of doing it.
Right now I have a route set up for our inventory system. You would access this route by typing in localhost::3000/inventory. There are buttons that come up on this main page which when clicked render that specific component. Instead of doing it that way, I would rather set up another route like /additem to the inventory path so that when I click on the Add Item button it will take me to the path localhost::3000/inventory/additem and render that component.
This is what our index.js file looks like for reference
import "babel-polyfill";
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'core-js/fn/array/find';
import 'core-js/fn/array/includes';
import 'core-js/fn/number/is-nan';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import ServiceReport from './imports/ServiceReportUI/ServiceReport'
import './StyleSheets/ServiceReport.css';
import InventorySystem from './imports/InventorySystem/InventorySystem.js';
import AddNewItemBtn from "./imports/InventorySystem/AddNewItemBtn";
import { BrowserRouter, Route, Link, Switch } from 'react-router-dom'
ReactDOM.render((
<BrowserRouter>
<Switch>
<Route exact path='/' component={App}/>
<Route path='/service' component={ServiceReport} />
<Route exact path='/inventory' component={InventorySystem} />
</Switch>
</BrowserRouter>), document.getElementById('appRoot'));
I'm thinking that adding another route like so will do the trick:
<Route exact path='/inventory/additem' component={AddItem} />
And I would access that route from within my InventorySystem.js file by doing something like this:
Class InventorySystem extends React.Component{
constructor(props){
super(props);
this.state = {}
}
goTo(e){
//go to Add Item path
}
render(){
return(
<button onClick={this.goTo.bind(this)}>Add Item</button>
)
}
}
I don't know much about React Router, and I'm also not sure if this is the right way of going about this, but any help or suggestions would be awesome!
The way to navigate within a React-Router setup is by using the Link component provided by the repo. Your first suggestion to create an additional Route for the AddItem component is correct. Simply import the Link component and define the expected path to go to.
import { Link } from "react-router-dom
class InventorySystem extends React.Component{
constructor(props){
super(props);
this.state = {}
}
render(){
return(
<Link to="/addItem">Add Item</Link>
)
}
}
You can style the Link to look like a button if needed as it does accept a className property.
You have 2 options, both included in example below
import { Link } from "react-router-dom";
class InventorySystem extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
goTo(e) {
// option 1
this.props.history.push('/inventory/additem');
}
render() {
return (
<div>
<button onClick={this.goTo.bind(this)}>Add Item</button> // option 1
<Link to="/inventory/additem">Add Item</Link> // option 2
</div>
)
}
}

Flow type system in React components

i start to use Flow in my React application and i have question about the type system.
I have a stateless component like this
export type LinkProps = {
to: string,
icon: React$Element<any>,
style: Object
}
const Link = ({
to,
icon,
style
}: LinkProps) => (
<sample>
</sample>
)
And another component where override some props
const NavLink = (props: LinkProps) => <Link icon={<Icon />} {...props} />
So, if i use the Link component directly i have the Flow type system but is not the case with the NavLink component.
Why i can use the NavLink component without respect the type system?
It sounds like you might need to add // #flow to the top of the file where you are using the NavLink component

Using browserHistory.push to change Route dynamically doesn't work with react-router v4

I try to do a simple redirect in a function...
I tried this:
Router.browserHistory.push('/dashboard');
But then I got this error:
Uncaught TypeError: Cannot read property 'push' of undefined
whats my fail?
Creating a new browserHistory won't work because <BrowserRouter> creates its own history instance, and listens for changes on that. So a different instance will change the url but not update the <BrowserRouter>.
browserHistory is not available form react-router-dom package from v4 , and is separated to history package.
Navigating with WithRouter
You can rather make use of withRouter Higher Order Component and navigate with history prop
From the official documentation
You can get access to the history object’s properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will re-render its component every time the route changes with the same props as <Route> render props: { match, location, history }.
Sample snippet
import {withRouter} from "react-router";
class MyComponent extends React.Component {
...
changeRoute = () => {
this.props.history.push('/dashboard)
}
...
}
export default withRouter(MyComponent);
Also see this answer for more info on nesting and dynamically routing in react-router-v4
Nesting routes and dynamically routing in React-router v4
As mentioned in the comments you should use this new approach for v4 react router but if you are looking for a quick workaround you can use context.
```javascript
import { PropTypes } from 'prop-types'
import React from 'react'
export default class MyComponent extends React.Component {
...
// redirect to dashboard
redirectToDashboard = () => {
this.context.router.history.push('/dashboard');
}
}
MyComponent.contextTypes = {
router: PropTypes.object
}
This should do the trick

React react-router-dom pass props to component

I need to pass props to component using router.
Here's my code:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import AppBarTop from './appbar/AppBarTop';
import Login from '../pages/login/Login';
import {BrowserRouter as Router, Route} from 'react-router-dom';
class App extends Component {
render() {
const { isAuthenticated } = this.props;
return (
<Router>
<div>
<AppBarTop isAuthenticated={isAuthenticated} />
<div className="content">
<Route path="/login" isAuthenticated={isAuthenticated} component={Login} />
</div>
</div>
</Router>
);
}
}
As you can see, isAuthenticated the prop i want to pass to Login component.
class Login extends Component {
constructor(props) {
super(props);
console.log(props);
}
render() {
return (
<LoginForm />
);
}
}
export default connect(null) (Login);
When i log the props the isAuthenticated prop is not there. What i'm doing wrong? How can i pass the prop correctly?
I followed the docs and also other discussions. From my understanding it should work.
The version of react-router and react-router-dom is 4.0.0
Pass it like this:
<Route
path="/login"
render={(props) => <Login {...props} isAuthenticated={isAuthenticated}/>}
/>
It should be available by this.props.isAuthenticated in Login Component.
Reason of {...props}:
If we don't write this then only isAuthenticated will get passed to Login component, all other values that router passes to component, will not be available inside Login component. When we write {...props} then we are passing all the values with one extra value.
And instead of using component with router use render method.
As per DOC:
Component:
When you use component (instead of render or children, below) the
router uses React.createElement to create a new React element from the
given component. That means if you provide an inline function to the
component attribute, you would create a new component every render.
This results in the existing component unmounting and the new
component mounting instead of just updating the existing component.
When using an inline function for inline rendering, use the render.
Render:
This allows for convenient inline rendering and wrapping without the
undesired remounting.

Categories