1
+ const deepEqual = require ( 'deep-equal' ) ;
1
2
2
- // constants
3
+ // Constants
3
4
4
5
const UPDATE_PATH = "@@router/UPDATE_PATH" ;
5
6
const SELECT_STATE = state => state . routing ;
6
7
7
8
// Action creator
8
9
9
- function updatePath ( path , avoidRouterUpdate ) {
10
+ function pushPath ( path , state , avoidRouterUpdate ) {
10
11
return {
11
12
type : UPDATE_PATH ,
12
13
path : path ,
14
+ state : state ,
15
+ replace : false ,
16
+ avoidRouterUpdate : ! ! avoidRouterUpdate
17
+ } ;
18
+ }
19
+
20
+ function replacePath ( path , state , avoidRouterUpdate ) {
21
+ return {
22
+ type : UPDATE_PATH ,
23
+ path : path ,
24
+ state : state ,
25
+ replace : true ,
13
26
avoidRouterUpdate : ! ! avoidRouterUpdate
14
27
}
15
28
}
@@ -18,25 +31,27 @@ function updatePath(path, avoidRouterUpdate) {
18
31
19
32
const initialState = {
20
33
changeId : 1 ,
21
- path : ( typeof window !== ' undefined' ) ?
22
- locationToString ( window . location ) :
23
- '/'
34
+ path : undefined ,
35
+ state : undefined ,
36
+ replace : false
24
37
} ;
25
38
26
39
function update ( state = initialState , action ) {
27
40
if ( action . type === UPDATE_PATH ) {
28
41
return Object . assign ( { } , state , {
29
42
path : action . path ,
30
- changeId : state . changeId + ( action . avoidRouterUpdate ? 0 : 1 )
43
+ changeId : state . changeId + ( action . avoidRouterUpdate ? 0 : 1 ) ,
44
+ state : action . state ,
45
+ replace : action . replace
31
46
} ) ;
32
47
}
33
48
return state ;
34
49
}
35
50
36
51
// Syncing
37
52
38
- function locationToString ( location ) {
39
- return location . pathname + location . search + location . hash ;
53
+ function locationsAreEqual ( a , b ) {
54
+ return a . path === b . path && deepEqual ( a . state , b . state ) ;
40
55
}
41
56
42
57
function syncReduxAndRouter ( history , store , selectRouterState = SELECT_STATE ) {
@@ -50,25 +65,36 @@ function syncReduxAndRouter(history, store, selectRouterState = SELECT_STATE) {
50
65
) ;
51
66
}
52
67
68
+ let historyLocation = { } ;
69
+
53
70
const unsubscribeHistory = history . listen ( location => {
54
- const routePath = locationToString ( location ) ;
71
+ historyLocation = {
72
+ path : history . createPath ( location ) ,
73
+ state : location . state
74
+ } ;
75
+
76
+ const routing = getRouterState ( ) ;
77
+
78
+ // Avoid dispatching an action if the store is already up-to-date,
79
+ // even if `history` wouldn't do anything if the location is the same
80
+ if ( locationsAreEqual ( routing , historyLocation ) ) return ;
55
81
56
- // Avoid dispatching an action if the store is already up-to-date
57
- if ( getRouterState ( ) . path !== routePath ) {
58
- store . dispatch ( updatePath ( routePath , { avoidRouterUpdate : true } ) ) ;
59
- }
82
+ store . dispatch ( pushPath ( historyLocation . path , historyLocation . state , { avoidRouterUpdate : true } ) ) ;
60
83
} ) ;
61
84
62
85
const unsubscribeStore = store . subscribe ( ( ) => {
63
86
const routing = getRouterState ( ) ;
64
87
65
- // Only update the router once per `updatePath ` call. This is
88
+ // Only update the router once per `pushPath ` call. This is
66
89
// indicated by the `changeId` state; when that number changes, we
67
- // should call `pushState`.
68
- if ( lastChangeId !== routing . changeId ) {
69
- lastChangeId = routing . changeId ;
70
- history . pushState ( null , routing . path ) ;
71
- }
90
+ // should update the history.
91
+ if ( lastChangeId === routing . changeId ) return ;
92
+
93
+ lastChangeId = routing . changeId ;
94
+
95
+ const method = routing . replace ? 'replaceState' : 'pushState' ;
96
+
97
+ history [ method ] ( routing . state , routing . path ) ;
72
98
} ) ;
73
99
74
100
return function unsubscribe ( ) {
@@ -79,7 +105,8 @@ function syncReduxAndRouter(history, store, selectRouterState = SELECT_STATE) {
79
105
80
106
module . exports = {
81
107
UPDATE_PATH ,
82
- updatePath,
108
+ pushPath,
109
+ replacePath,
83
110
syncReduxAndRouter,
84
111
routeReducer : update
85
112
} ;
0 commit comments