-
Notifications
You must be signed in to change notification settings - Fork 594
/
ConnectedRouter.js
135 lines (116 loc) · 4.19 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
134
135
import React, { PureComponent } 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 { 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 PureComponent {
constructor(props) {
super(props)
const { store, history, onLocationChanged } = props
this.inTimeTravelling = false
// Subscribe to store changes to check if we are in time travelling
this.unsubscribe = store.subscribe(() => {
// Extract store's location
const {
pathname: pathnameInStore,
search: searchInStore,
hash: hashInStore,
state: stateInStore,
} = getLocation(store.getState())
// Extract history's location
const {
pathname: pathnameInHistory,
search: searchInHistory,
hash: hashInHistory,
state: stateInHistory,
} = history.location
// If we do time travelling, the location in store is changed but location in history is not changed
if (
props.history.action === 'PUSH' &&
(pathnameInHistory !== pathnameInStore ||
searchInHistory !== searchInStore ||
hashInHistory !== hashInStore ||
stateInStore !== stateInHistory)
) {
this.inTimeTravelling = true
// Update history's location to match store's location
history.push({
pathname: pathnameInStore,
search: searchInStore,
hash: hashInStore,
state: stateInStore,
})
}
})
const handleLocationChange = (location, action, isFirstRendering = false) => {
// Dispatch onLocationChanged except when we're in time travelling
if (!this.inTimeTravelling) {
onLocationChanged(location, action, isFirstRendering)
} else {
this.inTimeTravelling = false
}
}
// Listen to history changes
this.unlisten = history.listen(handleLocationChange)
// Dispatch a location change action for the initial location.
// This makes it backward-compatible with react-router-redux.
// But, we add `isFirstRendering` to `true` to prevent double-rendering.
handleLocationChange(history.location, history.action, true)
}
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,
basename: PropTypes.string,
children: PropTypes.oneOfType([ PropTypes.func, PropTypes.node ]),
onLocationChanged: PropTypes.func.isRequired,
}
const mapDispatchToProps = dispatch => ({
onLocationChanged: (location, action, isFirstRendering) => dispatch(onLocationChanged(location, action, isFirstRendering))
})
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(null, mapDispatchToProps)(ConnectedRouterWithContext)
}
export default createConnectedRouter