/
ConnectedRouter.js
133 lines (114 loc) · 4.01 KB
/
ConnectedRouter.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect, ReactReduxContext } from 'react-redux'
import { Router } from 'react-router'
import { onLocationChanged } from './actions'
import createSelectors from './selectors'
const createConnectedRouter = (structure) => {
const { getIn } = structure
const { getRouter, getLocation } = createSelectors(structure)
/*
* ConnectedRouter listens to a history object passed from props.
* When history is changed, it dispatches action to redux store.
* Then, store will pass props to component to render.
* This creates uni-directional flow from history->store->router->components.
*/
class ConnectedRouter extends Component {
constructor(props) {
super(props)
this.inTimeTravelling = false
// Subscribe to store changes
this.unsubscribe = props.store.subscribe(() => {
// Extract store's location
const {
pathname: pathnameInStore,
search: searchInStore,
hash: hashInStore,
} = getLocation(props.store.getState())
// Extract history's location
const {
pathname: pathnameInHistory,
search: searchInHistory,
hash: hashInHistory,
} = props.history.location
// If we do time travelling, the location in store is changed but location in history is not changed
if (pathnameInHistory !== pathnameInStore || searchInHistory !== searchInStore || hashInHistory !== hashInStore) {
this.inTimeTravelling = true
// Update history's location to match store's location
props.history.push({
pathname: pathnameInStore,
search: searchInStore,
hash: hashInStore,
})
}
})
const handleLocationChange = (location, action) => {
// Dispatch onLocationChanged except when we're in time travelling
if (!this.inTimeTravelling) {
props.onLocationChanged(location, action)
} else {
this.inTimeTravelling = false
}
}
// Listen to history changes
this.unlisten = props.history.listen(handleLocationChange)
// Dispatch a location change action for the initial location
handleLocationChange(props.history.location, props.history.action)
}
componentWillUnmount() {
this.unlisten()
this.unsubscribe()
}
render() {
const { history, children } = this.props
return (
<Router history={history}>
{ children }
</Router>
)
}
}
ConnectedRouter.propTypes = {
store: PropTypes.shape({
getState: PropTypes.func.isRequired,
subscribe: PropTypes.func.isRequired,
}).isRequired,
history: PropTypes.shape({
action: PropTypes.string.isRequired,
listen: PropTypes.func.isRequired,
location: PropTypes.object.isRequired,
push: PropTypes.func.isRequired,
}).isRequired,
location: PropTypes.oneOfType([
PropTypes.object,
PropTypes.string,
]).isRequired,
action: PropTypes.string.isRequired,
basename: PropTypes.string,
children: PropTypes.oneOfType([ PropTypes.func, PropTypes.node ]),
onLocationChanged: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
action: getIn(getRouter(state), ['action']),
location: getIn(getRouter(state), ['location']),
})
const mapDispatchToProps = dispatch => ({
onLocationChanged: (location, action) => dispatch(onLocationChanged(location, action))
})
const ConnectedRouterWithContext = props => {
const Context = props.context || ReactReduxContext
if (Context == null) {
throw 'Please upgrade to react-redux v6'
}
return (
<Context.Consumer>
{({ store }) => <ConnectedRouter store={store} {...props} />}
</Context.Consumer>
)
}
ConnectedRouterWithContext.propTypes = {
context: PropTypes.object,
}
return connect(mapStateToProps, mapDispatchToProps)(ConnectedRouterWithContext)
}
export default createConnectedRouter