1
- import deepEqual from 'deep-equal'
1
+ import { createMemoryHistory } from 'history' ;
2
2
3
3
// Constants
4
4
5
5
export const UPDATE_PATH = '@@router/UPDATE_PATH'
6
6
const SELECT_STATE = state => state . routing
7
7
8
- export function pushPath ( path , state , { avoidRouterUpdate = false } = { } ) {
8
+ export function pushPath ( path , state ) {
9
9
return {
10
10
type : UPDATE_PATH ,
11
11
payload : {
12
12
path : path ,
13
13
state : state ,
14
14
replace : false ,
15
- avoidRouterUpdate : ! ! avoidRouterUpdate
16
15
}
17
16
}
18
17
}
19
18
20
- export function replacePath ( path , state , { avoidRouterUpdate = false } = { } ) {
19
+ export function replacePath ( path , state ) {
21
20
return {
22
21
type : UPDATE_PATH ,
23
22
payload : {
24
23
path : path ,
25
24
state : state ,
26
25
replace : true ,
27
- avoidRouterUpdate : ! ! avoidRouterUpdate
28
26
}
29
27
}
30
28
}
@@ -42,7 +40,7 @@ function update(state=initialState, { type, payload }) {
42
40
if ( type === UPDATE_PATH ) {
43
41
return Object . assign ( { } , state , {
44
42
path : payload . path ,
45
- changeId : state . changeId + ( payload . avoidRouterUpdate ? 0 : 1 ) ,
43
+ changeId : state . changeId + 1 ,
46
44
state : payload . state ,
47
45
replace : payload . replace
48
46
} )
@@ -52,10 +50,6 @@ function update(state=initialState, { type, payload }) {
52
50
53
51
// Syncing
54
52
55
- function locationsAreEqual ( a , b ) {
56
- return a != null && b != null && a . path === b . path && deepEqual ( a . state , b . state )
57
- }
58
-
59
53
function createPath ( location ) {
60
54
const { pathname, search, hash } = location
61
55
let result = pathname
@@ -66,32 +60,53 @@ function createPath(location) {
66
60
return result
67
61
}
68
62
69
- export function syncReduxAndRouter ( history , store , selectRouterState = SELECT_STATE ) {
63
+ export function syncReduxAndRouter ( browserHistory , store , selectRouterState = SELECT_STATE ) {
70
64
const getRouterState = ( ) => selectRouterState ( store . getState ( ) )
71
-
72
- // To properly handle store updates we need to track the last route.
73
- // This route contains a `changeId` which is updated on every
74
- // `pushPath` and `replacePath`. If this id changes we always
75
- // trigger a history update. However, if the id does not change, we
76
- // check if the location has changed, and if it is we trigger a
77
- // history update. It's possible for this to happen when something
78
- // reloads the entire app state such as redux devtools.
79
- let lastRoute = undefined
80
-
81
65
if ( ! getRouterState ( ) ) {
82
66
throw new Error (
83
67
'Cannot sync router: route state does not exist (`state.routing` by default). ' +
84
68
'Did you install the routing reducer?'
85
69
)
86
70
}
87
71
88
- const unsubscribeHistory = history . listen ( location => {
72
+ const memoryHistory = createMemoryHistory ( ) ;
73
+
74
+ let lastId = null ;
75
+ let storeListeningToBrowser = true ;
76
+ let browserListeningToStore = true ;
77
+
78
+ const unsubscribeStore = store . subscribe ( ( ) => {
79
+ const routing = getRouterState ( ) ;
80
+
81
+ if ( lastId !== routing . changeId ) {
82
+ lastId = routing . changeId ;
83
+
84
+ const method = routing . replace ? 'replace' : 'push'
85
+ console . log ( "sending to memoryHistory from store subscriber" ) ;
86
+ memoryHistory [ method ] ( {
87
+ pathname : routing . path ,
88
+ state : routing . state
89
+ } ) ;
90
+
91
+ if ( browserListeningToStore ) {
92
+ console . log ( "pushing to browserHistory from store subscriber" ) ;
93
+ storeListeningToBrowser = false ;
94
+ browserHistory [ method ] ( {
95
+ pathname : routing . path ,
96
+ state : routing . state
97
+ } ) ;
98
+ storeListeningToBrowser = true ;
99
+ }
100
+ }
101
+ } ) ;
102
+
103
+ const unsubscribeHistory = browserHistory . listen ( ( location ) => {
89
104
const route = {
90
105
path : createPath ( location ) ,
91
106
state : location . state
92
107
}
93
108
94
- if ( ! lastRoute ) {
109
+ if ( ! lastId ) {
95
110
// `initialState` *should* represent the current location when
96
111
// the app loads, but we cannot get the current location when it
97
112
// is defined. What happens is `history.listen` is called
@@ -108,42 +123,30 @@ export function syncReduxAndRouter(history, store, selectRouterState = SELECT_ST
108
123
state : route . state ,
109
124
replace : false
110
125
}
111
-
112
- // Also set `lastRoute` so that the store subscriber doesn't
113
- // trigger an unnecessary `pushState` on load
114
- lastRoute = initialState
115
-
116
- store . dispatch ( pushPath ( route . path , route . state , { avoidRouterUpdate : true } ) ) ;
117
- } else if ( ! locationsAreEqual ( getRouterState ( ) , route ) ) {
118
- // The above check avoids dispatching an action if the store is
119
- // already up-to-date
126
+ }
127
+ if ( storeListeningToBrowser ) {
128
+ console . log ( "got new location in browserHistory listener, sending to store" )
129
+ browserListeningToStore = false ;
120
130
const method = location . action === 'REPLACE' ? replacePath : pushPath
121
- store . dispatch ( method ( route . path , route . state , { avoidRouterUpdate : true } ) )
131
+ store . dispatch ( method ( route . path , route . state ) ) ;
132
+ browserListeningToStore = true ;
122
133
}
123
- } )
124
-
125
- const unsubscribeStore = store . subscribe ( ( ) => {
126
- let routing = getRouterState ( )
127
-
128
- // Only trigger history update if this is a new change or the
129
- // location has changed.
130
- if ( lastRoute . changeId !== routing . changeId ||
131
- ! locationsAreEqual ( lastRoute , routing ) ) {
132
-
133
- lastRoute = routing
134
- const method = routing . replace ? 'replace' : 'push'
135
- history [ method ] ( {
136
- pathname : routing . path ,
137
- state : routing . state
138
- } )
134
+ } ) ;
135
+
136
+ const reduxRouterHistory = Object . assign ( { } , memoryHistory , {
137
+ unsubscribe : function ( ) {
138
+ unsubscribeStore ( ) ;
139
+ unsubscribeHistory ( ) ;
140
+ } ,
141
+ push : function ( location ) {
142
+ console . log ( "Got push in reduxRouterHistory (i.e. <Link/>). sending to store" ) ;
143
+ const method = location . action === 'REPLACE' ? replacePath : pushPath
144
+ store . dispatch ( method ( createPath ( location ) , location . state ) ) ;
139
145
}
146
+ } ) ;
140
147
141
- } )
148
+ return reduxRouterHistory ;
142
149
143
- return function unsubscribe ( ) {
144
- unsubscribeHistory ( )
145
- unsubscribeStore ( )
146
- }
147
150
}
148
151
149
152
export { update as routeReducer }
0 commit comments