react-router-dom: 4.0.0
$ create-react-app
$ cd app
$ yarn add redux react-router-dom react-redux
which produces:
{
"name": "app",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^15.4.2",
"react-dom": "^15.4.2",
"react-redux": "^5.0.3",
"react-router-dom": "^4.0.0",
"redux": "^3.6.0"
},
"devDependencies": {
"react-scripts": "0.9.5"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
Then
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
const MOUNT_NODE = document.getElementById('root')
import { Provider } from 'react-redux'
import { BrowserRouter as Router } from 'react-router-dom'
import { createStore } from 'redux'
// dummy reducer
function dummy() {
return {}
}
const store = createStore(dummy)
ReactDOM.render((
<Provider store={store}>
<Router>
<App/>
</Router>
</Provider>
), MOUNT_NODE)
// src/App.js
import React from 'react'
import { Link, Route } from 'react-router-dom'
import { connect } from 'react-redux'
function App() {
return (
<div className="app">
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/profile">Profile</Link></li>
</ul>
<div className="routes">
<Route path="/" render={() => (
<h1>Home</h1>
)}/>
<Route path="/profile" render={() => (
<h1>Profile</h1>
)} />
</div>
</div>
)
}
export default connect()(App)
Render <h1>Profile</h1> without reload when <Link to="/profile">Profile</Link> clicked.
Doesn't re-render except this line:
export default connect()(App)
changed to
export default App
I've found that if you have the NODE_ENV variable set to production, the connect will block component updates. But when I ran in development mode, routes changed normally.
I fixed it by writing a very simple wrapper:
+class HistoryPusher extends React.Component {
+ static propTypes = {
+ location: React.PropTypes.object
+ }
+
+ static defaultProps = {
+ location: {}
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (!this.props.location || !nextProps.location) {
+ return
+ }
+ if (this.props.location.pathname !== nextProps.location.pathname) {
+ store.dispatch(setLocation(location))
+ }
+ }
+
+ render() {
+ return this.props.children
+ }
+}
+
+const HistoryPusherWithRouter = withRouter(HistoryPusher)
+
const rootEl = document.getElementById('content')
const wrapApp = AppComponent =>
(<Provider store={store} key="provider">
<BrowserRouter>
- <AppContainer>
- <AppComponent store={store} client={client} />
- </AppContainer>
+ <HistoryPusherWithRouter>
+ <AppContainer>
+ <AppComponent store={store} client={client} />
+ </AppContainer>
+ </HistoryPusherWithRouter>
</BrowserRouter>
</Provider>)
And then adding a location path in my redux store and connecting that path to my App component:
@connect(
state => ({
user: state.auth.user,
+ location: state.location,
}), { logout })
export default class App extends Component {
static propTypes = {
It's also possible to wrap your AppComponent in withRouter decorator:
- export default connect()(App)
+ export default withRouter(connect()(App))
Not sure about performance consequences, though.
Most helpful comment
It's also possible to wrap your
AppComponentinwithRouterdecorator:Not sure about performance consequences, though.