React Router v4 prevent from running parent route component - reactjs

I'm building an article search with React [15.6.1] and Router [4.1.1] and noticed that when trying to access an article directly the previous component is loaded, even thou it's not the one that's being called
// === Layout ==================
class Layout extends React.Component {
render() {
return (
<Provider store={this.props.store}>
<HashRouter>
<div>
<Switch>
<Route exact path="/" component={SearchFilter} />
<Route path="/article/:guid" component={Article} />
</Switch>
</div>
</HashRouter>
</Provider>
);
}
}
// === SearchFilter ==================
class SearchFilter extends React.Component {
componentDidMount() {
console.log('SearchFilter Did Mount');
}
render() {
return (
<div>...</div>
);
}
}
// === Article ==================
class Article extends React.Component {
componentDidMount() {
console.log('Article Did Mount');
}
render() {
return (
<div>...</div>
);
}
}
So when going to the root localhost:3000/#/ it prints
// SearchFilter Did Mount
And when I access an article directly like this localhost:3000/#/article/123456 it prints
// SearchFilter Did Mount
// Article Did Mount
So my question is, how can I prevent it from running the previous route?
Because I would like to dispatch some actions there that would trigger some ajax calls to the webservice.
Thanks

Try this instead :
<HashRouter basename="/">
<div>
<Switch>
<Route exact path="/search" component={SearchFilter} />
<Route path="/article/:guid" component={Article} />
</Switch>
</div>
</HashRouter>

Edit:
For me its working well... So like said... there is something really weird and hidden happening on your machine, or you just put / and then rewrite url to /#/article/123 and it cause the first log stays in the console, but its from the previsous url "/" and if you reload the browser by F5 on the new url /#/article/123 you will see only the "Article did mount"

Related

React Router returning blank screen

Can't seem to figure out what's wrong even browsing through a couple of examples of react-router-dom...
So here's my main page:
class MyPage extends Component {
render () {
return (
<Aux>
<NavigationHeader />
<Switch>
<Route path="/me" exact component={PersonalInfoContainer}/>
<Route path="/exps" component={ExpsContainer}/>
</Switch>
{/* <ExpsContainer /> */}
</Aux>
);
}
I want to create a page to show some personal info of the user as the /me page. So here's the PersonalInfoContainer:
class PersonalInfoContainer extends Component {
componentDidMount () {
console.log("PI container: ", this.props);
}
render () {
return (
<Aux>
<Link to={this.props.match.url + '/edit' }>EDIT</Link>
<Switch>
<Route path={this.props.match.url + '/edit'} component={PersonalInfoForm}/>
<Route path={this.props.match.url} component={PersonalInfoDetail}/>
</Switch>
</Aux>
)
}
}
Here's the problem, what I want to do is have an EDIT button that leads to an input form to let users patch their info. I assume with this setup, the link should be /me/edit, but no matter I use Link to={this.props.match.url + '/edit' } or Link to={'/edit' } (which in this case the url is simply /edit), it always returns a blank page. I have tested that form component to be working so it really should be something wrong with my routing method.
Thanks for any help.

How to re-render the same component being used in different routes?

I have several routes rendering the same component. Depending on the route I want the component to fetch different data. However since I keep rendering the same component, React doesn't see any changes to the DOM when I click a Link tag (from my nav bar located in the Layout component) to another route rendering that same component. Meaning the component is not re-rendered with the new data. Here are my routes:
class App extends Component {
render() {
return (
<BrowserRouter>
<Provider store={store}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/fashion" component={PostTypePageContainer} />
<Route exact path="/beauty" component={PostTypePageContainer} />
</Switch>
</Layout>
</Provider>
</BrowserRouter>
);
}
}
export default App;
Here is the PostTypePageContainer component that I want to re-render with the new data each time:
class PostTypePageContainer extends Component {
componentDidMount() {
let route;
switch (this.props.location.pathname) {
case '/fashion':
route = '/fashion';
break;
case '/beauty':
route = '/beauty';
break;
default:
console.log('No data was found');
}
let dataURL = `http://localhost:8888/my-site//wp-json/wp/v2${route}?_embed`;
fetch(dataURL)
.then(res => res.json())
.then(res => {
this.props.dispatch(getData(res));
});
}
render() {
let posts = this.props.postData.map((post, i) => {
return <PostTypePage key={i} props={post} />;
});
return <div>{posts}</div>;
}
}
const mapStateToProps = ({ data }) => ({
postData: data.postData
});
export default connect(mapStateToProps)(PostTypePageContainer);
How do I go about re-rendering that component each time?
This is intended behavior of react-router.
While i suggest you create a HOC to fetch the data from different locations and pass it to the PostTypePageContainer via props, using a key will give you a quick work around that will cause your component to remount.
class App extends Component {
render() {
return (
<BrowserRouter>
<Provider store={store}>
<Layout>
<Switch>
<Route exact path="/" component={Home} />
<Route exact key={uniqueKey} path="/fashion" component={PostTypePageContainer} />
<Route exact key={someOtherUniqueKey} path="/beauty" component={PostTypePageContainer} />
</Switch>
</Layout>
</Provider>
</BrowserRouter>
);
}
}
export default App;
Source: https://github.com/ReactTraining/react-router/issues/1703
I wasn't able to get the <Route key={...} ... /> to work in my case. After trying several different approaches the one that worked for me was using the componentWillReceiveProps function in the reused component. This was getting called each time the component was called from a <Route />
In my code, I did:
componentWillReceiveProps(nextProps, nextContext) {
// When we receive a call with a new tag, update the current
// tag and refresh the content
this.tag = nextProps.match.params.tag;
this.getPostsByTag(this.tag);
}

react-router-dom: getting props.location from within <BrowserRouter> component

I have a simple App that uses BrowserRouter from 'react-router-dom' v4. I'm trying to access the location.pathname property from within the <BrowserRouter/> component, without avail:
class App extends Component{
render(){
return (
<BrowserRouter>
// How do I access this.props.location?
<div className={(this.props.location.pathnme === "/account") ? "bgnd-black" : "bgnd-white"} >
<Switch>
<Route path="/login" component={LoginPage}/>
<Route path="/success" component={LoginSuccess}/>
<Route path="/account" component={MyAccount}/>
...
<Route component={Error404}/>
</Switch>
</div>
</BrowserRouter>
);
}
}
I know that I can access the app's current path location through the child components with this.props.location.pathname, but I need to access it from the parent component, just below <BrowserRouter/> to run additional logic that doesn't pertain to child components. How can I get this location?
You can also do it using withRouter which has a similar result to putting the code in a render parameter and avoids the need for a "fake" <Route/>.
Essentially you put the JSX that needs to know the location in a component of its own, which is wrapped by withRouter. This supplies the location to the component:
import { withRouter } from 'react-router-dom';
const Content = withRouter(props =>
<div className={(props.location.pathname === "/account") ? "backg...
...
</div>
);
Then you use that in your main router section:
class App extends Component{
render() {
return (
<BrowserRouter>
<Content/>
...
Since react-router v5.1.0 you can use useLocation.
https://reactrouter.com/web/api/Hooks/uselocation
class App extends Component{
render(){
const location = useLocation();
return (
<div className={(location.pathname === "/account") ? "bgnd-black" : "bgnd-white"} >
//...
</div>
);
}
}
// ...
<BrowserRouter>
<App />
</BrowserRouter>
After digging through their GitHub issues, I found the solution. I must render a <Route /> within <BrowserRouter /> and pass the rest of my app into its render() function with history as a parameter. Within the render function, I can find the app's location in history.location.pathname.
class App extends Component{
render(){
return (
<BrowserRouter>
// We must add a parent <Route> and render its children while passing 'history' as parameter
<Route path={Paths.reserve} render={(history) =>
// Within render(), we can find it in history.location.pathname
<div className={(history.location.pathname === "/account") ? "background-black" : "background-white"} >
<Switch>
<Route path="/login" component={LoginPage}/>
<Route path="/success" component={LoginSuccess}/>
<Route path="/account" component={MyAccount}/>
...
<Route component={Error404}/>
</Switch>
</div>
}/>
}} />
</BrowserRouter>
);
}
}
This will update the history parameter automatically, without having to re-render on componentDidMount() or componentDidUpdate()
You achieve what u have asked for by doing this
import AccessRoute from './AccessRoute'
class App extends Component{
render(){
return (
<BrowserRouter>
<AccessRoute>
<div className={(this.props.location.pathnme === "/account") ? "bgnd-black" : "bgnd-white"} >
<Switch>
<Route path="/login" component={LoginPage}/>
<Route path="/success" component={LoginSuccess}/>
<Route path="/account" component={MyAccount}/>
...
<Route component={Error404}/>
</Switch>
</div>
</AccessRoute>
</BrowserRouter>
);
}
}
AccessRoute.jsx
import React from 'react'
import {withRouter} from 'react-router';
class AccessRoute extends React.Component{
constructor(props){
super(props);
}
//If you want to find the location on mount use this
componentDidMount(){
console.log("the path name is ",this.props.location.pathname);
}
//If you want to find the location on change use this
componentDidUpdate(prevprops){
if(this.props.location.pathname!=prevprops.location.pathname){
console.log("the new path name is ",this.props.location.pathname);
}
}
render(){
return(
this.props.children
);
}
}
export default withRouter(AccessRoute)

React router 4 not updating when using children prop on history.push

I have this piece of code:
class Base extends Component {
changeRoute = () => {
// after this, address bar gets updated but I can't see the Users
// component, only Home component.
this.props.history.push('/users');
}
render() {
return (
<div>
<MyBar />
{this.props.children}
</div>
)
}
}
class App extends Component {
render() {
return (
<Router>
<Base>
<a onClick={this.changeRoute}>Change</a>
<Route path="/home" component={Home} />
<Route path="/users" component={Users} />
</Base>
</Router>
)
}
}
However, when I try to change location by either using history.push or push from react-router-redux I can see the browser path updated but the content is not updating, it shows the original path content. I also noted that whenever I refresh the browser I can see the correct content related to the path.
When I change the code to the following, both the browser path and the content updates accordingly as expected, my question is: Why is it behaving different? if in my opinion both codes do the same thing.
class Base extends Component {
render() {
return (
<MyBar />
)
}
}
class App extends Component {
changeRoute = () => {
// after this, address bar and component are updated as expected.
this.props.history.push('/users');
}
render() {
return (
<Router>
<div>
<Base />
<a onClick={this.changeRoute}>Change</a>
<Route path="/home" component={Home} />
<Route path="/users" component={Users} />
</div>
</Router>
)
}
}
Edit:
I added to the source code the push method. This is the piece of code I'm using to export the App component:
import {withRouter} from 'react-router';
export default withRouter(App);
This is the code I ran and it is running fine. Can you please check whether there is anything I need to change in my code to reproduce your issue?
import React from 'react';
import ReactDOM from 'react-dom';
import {NavLink, BrowserRouter,Route} from 'react-router-dom';
class Base extends React.Component {
render() {
return (
<div>
<div>MyBar</div>
{this.props.children}
</div>
)
}
}
class App extends React.Component {
render() {
return (
<BrowserRouter>
<Base>
<NavLink to="/users">Change</NavLink>
<Route path="/home" render={()=>"Home"} />
<Route path="/users" render={()=>"Users"} />
</Base>
</BrowserRouter>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
Edit: From what I understood, you want to be able to navigate to the user page when the change link is clicked. The correct way to do it is using the NavLink control. This would not require the use of withRouter HOC which should only be used inside a Router component and not outside it.

React-Router route component is constructed twice on page refresh

I have a component that boots up a timer with setInterval in the componentDidMount in my component associated with /testRoute.
That looks like:
componentDidMount() {
if (this.timer == null) {
var timerFunction = function() { this.tick() }.bind(this)
this.timer = setInterval(timerFunction, 500);
}
}
I have a balancing line of code to remove the timer when the component 'goes away' (not sure to what extent this lifecycle hook is equivalent to the object dying, e.g. getting deallocated),
componentWillUnmount() {
clearInterval(this.timer)
}
The problem is that if I'm on route /testRoute and I refresh the browser, constructor() is called twice, and so is componentDidMount. At this point, there are two "timers" ticking. If I navigate to another route, componentWillUnmount is called and one of the timers is cleared. But that still leaves the one.
Is it expected behavior that my component should hit componentDidMount twice on page refresh? And if so, how can I ensure that only one timer is ever setup?
edit:
-demonstrating this with a skeleton example:
function Nav() {
return (
<ul className='nav'>
<li><Link to='/test'>test</Link></li>
</ul>
)
}
With a single route:
class App extends React.Component {
render() {
return (
<Router>
<div>
<Nav />
<Route path='/test' component={Test} />
</div>
</Router>
)
}
}
And a trivial component:
class Test extends React.Component {
constructor() {
super()
console.log("constructor")
}
render() {
return <div>test</div>
}
}
In this code: "constructor" is logged to the console twice on refreshing /test.
I have similar issue which my component mounted twice. In my case, I make a request in componentDidMount which called twice. I render some of Route by taking from server by async call (App.ROUTES), and some as static (routeInfo.insideDashboard). And the static components mount twice.
<Dashboard specialMenu={this.state.menu}>
<Switch>
{routeInfo.insideDashboard.map(route =>
<Route exact key={route.path} path={route.path} component={Auth(route.component)}/>)}
{App.ROUTES.map(route => <Route key={route.urlPath} exact path={"/" + route.urlPath}
component={Auth(asyncComponent(route.importPath, {title: route.title}))}/>)}
<Route component={NotFound}/>
</Switch>
</Dashboard>
I suspect that the reason can be render of static routes (routeInfo.insideDashboard) twice, before other routes come from server and after it. I add a check routesFromServerTaken before render Routes which ensures Route (definitions) render only once.
<Dashboard specialMenu={this.state.menu}>
<Switch>
{routesFromServerTaken ? routeInfo.insideDashboard.map(route =>
<Route exact key={route.path} path={route.path} component={Auth(route.component)}/>): null}
{App.ROUTES.map(route => <Route key={route.urlPath} exact path={"/" + route.urlPath}
component={Auth(asyncComponent(route.importPath, {title: route.title}))}/>)}
<Route component={NotFound}/>
</Switch>
</Dashboard>
This solved my problem, components mount only once now.

Resources