React-Router route component is constructed twice on page refresh - reactjs

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.

Related

React router conditional routes not working

I have a couple of routes I want to show only when a certain condition is met. If this condition is met, and the routes are enabled navigating to them through the URL is not possible, and the Redirect always gets hit
constructor(props: {}) {
super(props);
this.state = {
configuration: new Configuration({}),
}
async function GetConfiguration() {
try {
var response = await fetch("/getconfiguration");
return await response.json();
}
catch (error) {
console.log(error);
}
}
async componentDidMount() {
...
var configuration = await GetConfiguration();
this.setState({ configuration: configuration });
...
}
render() {
...
<Router>
<Switch>
<Route exact path="/my-account">
<div className='dw-side-menu'></div>
...
</Route>
{this.state.configuration.shouldRoute && <Route exact path="/my-company"><div>my company</div></Route>}
{this.state.configuration.shouldRoute && <Route exact path="/user-management"><div>user management</div></Route>}
<Redirect to="/my-account" />
</Switch>
</Router>
...
}
The awkward thing is that, when I click on the Link somewhere else on the page, the routing to, e.g. /my-company, works - but not if I type the URL into the browser it only goes to the redirect, as if the routes are not there at all. Also if I hit refresh when on /my-company I get redirected back to the my-account. Without the conditions everything is working fine.
Without Redirect, entering the URLs and Refreshing the browser works as expected, just that I don't get redirected when a route is not recognized.
What am I doing wrong?
As suspected in my comment, this.state.configuration.shouldRoute is undefined on first render.
One workaround for this would be to actually display a loading state until you have loaded the configuration, like so:
// in render(), before your other return
if(Object.keys(this.state.configuration).length === 0){
// config has not been loaded yet
return (<MyCoolLoadingComponent />)
}
If displaying a loading indicator before the config is loaded is an option, go with it. If it is not, you have to rethink your architecture.
To make your code cleaner, with less bugs, put sections of codes into individual components. Then you could say:
class SideMenu extends component {
render(
<div className='dw-side-menu'></div>
...
)
}
class MyCompany extends component {
render(
<div>my company</div>
...
)
}
class UserManagement extends component {
render(
<div>user management</div>
...
)
}
render() {
...
<Router>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/my-account" component={MyAccount} />
<Route path="/my-company" component={MyCompany} />
<Route path="/user-management" component={UserManagement} />
<Redirect to="/my-account" />
</Switch>
</Router>
...
}
Cleaner code with less issues. Hope this helps. You can as well use some of the powerful features in react-router-dom; location, history and match. Thanks.

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 v4 prevent from running parent route component

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"

React Router causing component remount on Firebase update

I have an App component which, using react-router, holds a few components in two routes. I also have a Firebase data store which I want to bind to the state of App (using rebase) so I can pass it down to any component I wish as a prop. This is my App class:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: {}
};
}
componentDidMount () {
rebase.bindToState('items', {
context: this,
state: 'items'
})
}
render() {
return (
<Router>
<div className='container'>
<div className="header">
<h3>Header</h3>
<Nav/>
</div>
<Switch>
<Route exact path='/' component={() => <Home items={this.state.items} rebase={rebase} />} />
<Route render={function () {
return <p>Not Found</p>
}} />
</Switch>
</div>
</Router>
)
}
}
Now, when I load my page I get two mounts of the Home component. This in itself is not great. However, I have several actions in the Home component that use rebase to modify/read from Firebase. As a callback of these actions they also change the Home component's state. The problem is, whenever I do a Firebase call, it remounts the Home component and any state I have is lost.
If I remove the Router wrappers from the Home component, and render it purely as render( <Home items={this.state.items} rebase={rebase} /> ), my app works perfectly as intended. I don't know why wrapping it in Router stuff makes it not work. I thought it was because I had additional URL parameters that also changed when I call firebase updates (e.g. /?p=sgergwc4), but I have a button that changes that parameter without a firebase update and it doesn't cause any problems (i.e. doesn't cause a remount). So what's up with the Router?
Turns out the answer is simple; instead of component={}, I should use render={}. Fixes everything. It was in the docs too.

react-router : share state between Routes without Redux

I would like to have a shared state (list of clients fetched remotely) between 2 sibling Routes : Timesheets and Clients.
I want to try how far i can go with 'pure' React (No Flux architecture).
This example works, but I have an error : browser.js:49 Warning: [react-router] You cannot change <Router routes>; it will be ignored
So, it doesn't seem to like async props.
constructor(props) {
super(props);
this.state = {
clients : []
}
}
componentDidMount() {
fetch("clients.json")
.then(response => response.json())
.then(clients => this.setState({ clients }));
}
render() {
return (
<Router history={browserHistory}>
<Route path="/" component={Header} >
<Route path="timesheet" component={() => (<Timesheets {...this.state} />) }/>
<Route path="clients" component={() => (<Clients {...this.state} />) }/>
</Route>
</Router>
);
}
Is it possible to send async props down to each route?
Or is it possible to set the whole state in the parent route (Header component) and then access this state from each child route (Timesheets and Clients components)?
You can use an high-order component to fetch and inject data to your top level component. Then you can pass props to sub routes via React.cloneElement.
HOC
const FetchHOC = url => Component => class extends React.Component() {
state = { data: null };
componentDidMount() {
fetch(url).then(data => this.setState({ data }));
}
render() {
return <Component {...this.props} {...this.state.data} />;
}
}
Route configuration
<Route path="/" component={FetchHOC('http://some-url')(App)}>
<Route path="subroute1" component={SubRoute1} />
<Route path="subroute2" component={SubRoute2} />
</Route>
Parent route render()
<div className="App">
{this.props.children && React.cloneElement(this.props.children, {
data: this.props.data,
// ...
})}
</div>
You can take a look at reacts Context. Redux also makes use of Context. It allows you to pass some data down to all children. But you should write code to use this data, for instance you have determine contextTypes etc.
You can see details on docs about how to use it.

Resources