Reputation: 2855
I'm building a simple app in Meteor with React, and I'm using React Router 4.
There is a button which creates a new map, and when the server returns the new map's id, the browser should navigate to the page that shows the newly created map.
I have got it nearly working but when I click the button, the URL changes in the address bar but the map component doesn't render. Refreshing the page doesn't help. But it works fine if I click the new map's link in the list of maps.
This code is ugly and I'm sure it's not the best way to do any of it, but it is literally the only way I've been able to make it work at all. I've read all the forum posts and docs I can find but nothing is clear to me.
I'd be very grateful for any help.
Main.js
```JSX
/* global document */
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
// Start the app with imports
import '/imports/startup/client';
import '../imports/startup/accounts-config.js';
import App from '../imports/ui/layouts/App.jsx';
Meteor.startup(() => {
render((<BrowserRouter><App /></BrowserRouter>), document.getElementById('app'));
});
App.js:
import React, { Component } from 'react';
import { withRouter, Switch, Route, Link, Redirect } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
// Mongodb collection
import { Maps } from '../../api/maps/maps.js';
// User Accounts
import AccountsUIWrapper from '../AccountsUIWrapper.jsx';
import Home from '../pages/home';
import Map from '../pages/map';
import About from '../pages/about';
class App extends Component {
renderTestButton() {
return (
<button onClick={this.handleClick.bind(this)}>New Map</button>
);
}
handleClick() {
let that = this;
Meteor.call('newMap', {'name': 'new map'}, function(error, result) {
that.props.history.push(`/map/${result}`);
});
}
render() {
let newMap = this.renderNewMap();
let testButton = this.renderTestButton();
return (
<div className="primary-layout">
<header>
<AccountsUIWrapper />
{testButton}
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
</nav>
</header>
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route exact path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
<Route path='/about' component={About}/>
</Switch>
</main>
</div>
);
}
}
export default withRouter(withTracker(() => {
const mapsHandle = Meteor.subscribe('maps');
return {
'maps': Maps.find({}).fetch(),
'loading': !mapsHandle.ready(),
'currentUser': Meteor.user(),
};
})(App));
EDIT: I had a typo in my path, I wrote that.props.history.push(
/maps/${result});
when it should be that.props.history.push(
/map/${result});
to match the defined route.
I've correct the code, it now works but I still feel this can't be the best solution...
Upvotes: 1
Views: 2542
Reputation: 2855
After finding a typo in my original code ('/maps/' where the path should have been '/map/') I found another 'gotcha' which is this:
If the route expects a URL parameter, and it is not supplied, then the route doesn't seem to render at all. My route is defined as:
```JSX
<Route path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
If you try to navigate to 'http://localhost:3000/map/' then the component doesn't render. If you put any value on the end e.g. 'http://localhost:3000/map/dummyvalue' it renders.
I've now got a tidier version working:
```JSX
import React, { Component } from 'react';
import { withRouter, Switch, Route, Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data';
// Mongodb collection
import { Maps } from '../../api/maps/maps.js';
// User Accounts
import AccountsUIWrapper from '../AccountsUIWrapper.jsx';
import Home from '../pages/home';
import Map from '../pages/map';
import About from '../pages/about';
function NewMap(props) {
function handleClick(e) {
e.preventDefault();
let history = props.history;
Meteor.call('newMap', {'name': 'new map'}, (error, result) => {
history.push(`/map/${result}`);
});
}
let disabled = 'disabled';
if (Meteor.userId()) { disabled = '';}
return (
<button disabled={disabled} onClick={handleClick}>New Map</button>
);
}
class App extends Component {
renderNewMap() {
const history = this.props.history;
return (
<NewMap history={history}
/>
);
}
render() {
let newMap = this.renderNewMap();
return (
<div className="primary-layout">
<header>
<AccountsUIWrapper />
{newMap}
<nav>
<ul>
<li><Link to='/'>Home</Link></li>
<li><Link to='/about'>About</Link></li>
</ul>
</nav>
</header>
<main>
<Switch>
<Route exact path='/' component={Home}/>
<Route path="/map/:_id" render={({ match }) => (
<Map
params={match.params}
/>
)} />
<Route path='/about' component={About}/>
</Switch>
</main>
</div>
);
}
}
export default withRouter(withTracker(() => {
const mapsHandle = Meteor.subscribe('maps');
return {
'maps': Maps.find({}).fetch(),
'loading': !mapsHandle.ready(),
'currentUser': Meteor.user(),
};
})(App));
Upvotes: 1