Skip to content
This repository was archived by the owner on Oct 26, 2018. It is now read-only.

Commit 151154f

Browse files
committed
Tweak the API to handle initial state correctly
1 parent b6a9c1b commit 151154f

File tree

6 files changed

+202
-76
lines changed

6 files changed

+202
-76
lines changed

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,18 @@ const reducer = combineReducers(Object.assign({}, reducers, {
6161
routing: routeReducer
6262
}))
6363

64-
// Sync dispatched route actions to the history
64+
// specify the history to listen to
6565
const reduxRouterMiddleware = syncHistory(browserHistory)
66-
const createStoreWithMiddleware = applyMiddleware(reduxRouterMiddleware)(createStore)
67-
68-
const store = createStoreWithMiddleware(reducer)
66+
const store = createStore(
67+
reducer,
68+
applyMiddleware(reduxRouterMiddleware)
69+
)
6970

70-
// Required for replaying actions from devtools to work
71-
reduxRouterMiddleware.listenForReplays(store)
71+
// begin syncing
72+
reduxRouterMiddleware.syncWith(store, {
73+
urlToState: true, // route changes will appear in state
74+
stateToUrl: false // set to true for time travel in DevTools
75+
})
7276

7377
ReactDOM.render(
7478
<Provider store={store}>
@@ -140,7 +144,7 @@ Examples from the community:
140144

141145
_Have an example to add? Send us a PR!_
142146

143-
### API
147+
### API (TODO)
144148

145149
#### `syncHistory(history: History) => ReduxMiddleware`
146150

examples/basic/app.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,18 @@ const DevTools = createDevTools(
2727
</DockMonitor>
2828
)
2929

30-
const finalCreateStore = compose(
31-
applyMiddleware(middleware),
32-
DevTools.instrument()
33-
)(createStore)
34-
const store = finalCreateStore(reducer)
35-
middleware.listenForReplays(store)
30+
const store = createStore(
31+
reducer,
32+
compose(
33+
applyMiddleware(middleware),
34+
DevTools.instrument()
35+
)
36+
)
37+
38+
middleware.syncWith(store, {
39+
urlToState: true,
40+
stateToUrl: true
41+
})
3642

3743
ReactDOM.render(
3844
<Provider store={store}>

examples/basic/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
"react-dom": "^0.14.2",
1010
"react-redux": "^4.0.0",
1111
"react-router": "^1.0.0",
12-
"redux": "^3.0.4",
13-
"react-router-redux": "^2.1.0"
12+
"react-router-redux": "^2.1.0",
13+
"redux": "^3.2.1"
1414
},
1515
"devDependencies": {
1616
"babel-core": "^6.1.21",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"karma-webpack": "^1.7.0",
6363
"mocha": "^2.3.4",
6464
"react": "^0.14.3",
65-
"redux": "^3.0.4",
65+
"redux": "^3.2.1",
6666
"redux-devtools": "^3.0.0",
6767
"redux-devtools-dock-monitor": "^1.0.1",
6868
"redux-devtools-log-monitor": "^1.0.1",

src/index.js

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,7 @@ export function syncHistory(history) {
4949

5050
history.listen(location => { initialState.location = location })()
5151

52-
function middleware(store) {
53-
unsubscribeHistory = history.listen(location => {
54-
currentKey = location.key
55-
if (syncing) {
56-
// Don't dispatch a new action if we're replaying location.
57-
return
58-
}
59-
60-
store.dispatch(updateLocation(location))
61-
})
62-
63-
connected = true
64-
52+
function middleware() {
6553
return next => action => {
6654
if (action.type !== TRANSITION || !connected) {
6755
return next(action)
@@ -72,41 +60,72 @@ export function syncHistory(history) {
7260
}
7361
}
7462

75-
middleware.listenForReplays =
76-
(store, selectLocationState = SELECT_LOCATION) => {
77-
const getLocationState = () => selectLocationState(store.getState())
78-
const initialLocation = getLocationState()
79-
80-
unsubscribeStore = store.subscribe(() => {
81-
const location = getLocationState()
63+
middleware.syncWith =
64+
(store, {
65+
urlToState = false,
66+
stateToUrl = false,
67+
selectLocationState = SELECT_LOCATION
68+
} = {}) => {
69+
if (!urlToState && !stateToUrl) {
70+
throw new Error(
71+
'At least one of "urlToState" and "stateToUrl" options must be true.'
72+
)
73+
}
8274

83-
// If we're resetting to the beginning, use the saved initial value. We
84-
// need to dispatch a new action at this point to populate the store
85-
// appropriately.
86-
if (location.key === initialLocation.key) {
87-
history.replace(initialLocation)
88-
return
75+
if (stateToUrl) {
76+
const getLocationState = () => selectLocationState(store.getState())
77+
const initialLocation = getLocationState()
78+
79+
const reconcileLocationWithState = () => {
80+
const location = getLocationState()
81+
82+
// If we're resetting to the beginning, use the saved initial value. We
83+
// need to dispatch a new action at this point to populate the store
84+
// appropriately.
85+
if (location.key === initialLocation.key) {
86+
history.replace(initialLocation)
87+
return
88+
}
89+
90+
// Otherwise, if we need to update the history location, do so without
91+
// dispatching a new action, as we're just bringing history in sync
92+
// with the store.
93+
if (location.key !== currentKey) {
94+
syncing = true
95+
history.transitionTo(location)
96+
syncing = false
97+
}
8998
}
9099

91-
// Otherwise, if we need to update the history location, do so without
92-
// dispatching a new action, as we're just bringing history in sync
93-
// with the store.
94-
if (location.key !== currentKey) {
95-
syncing = true
96-
history.transitionTo(location)
97-
syncing = false
100+
unsubscribeStore = store.subscribe(reconcileLocationWithState)
101+
reconcileLocationWithState()
102+
}
103+
104+
if (urlToState) {
105+
unsubscribeHistory = history.listen(location => {
106+
currentKey = location.key
107+
if (syncing) {
108+
// Don't dispatch a new action if we're replaying location.
109+
return
110+
}
111+
112+
store.dispatch(updateLocation(location))
113+
})
114+
115+
connected = true
116+
}
117+
118+
return () => {
119+
if (stateToUrl) {
120+
unsubscribeStore()
98121
}
99-
})
100-
}
101122

102-
middleware.unsubscribe = () => {
103-
unsubscribeHistory()
104-
if (unsubscribeStore) {
105-
unsubscribeStore()
123+
if (urlToState) {
124+
unsubscribeHistory()
125+
connected = false
126+
}
127+
}
106128
}
107129

108-
connected = false
109-
}
110-
111130
return middleware
112131
}

test/createTests.js

Lines changed: 115 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,18 @@ expect.extend({
2828
}
2929
})
3030

31-
function createSyncedHistoryAndStore(createHistory) {
31+
function createSyncedHistoryAndStore(createHistory, syncOptions, initialState) {
3232
const history = createHistory()
3333
const middleware = syncHistory(history)
34-
const { unsubscribe } = middleware
35-
36-
const createStoreWithMiddleware = applyMiddleware(middleware)(createStore)
37-
const store = createStoreWithMiddleware(combineReducers({
34+
const reducer = combineReducers({
3835
routing: routeReducer
39-
}))
36+
})
37+
const store = createStore(
38+
reducer,
39+
initialState,
40+
applyMiddleware(middleware)
41+
)
42+
const unsubscribe = middleware.syncWith(store, syncOptions)
4043

4144
return { history, store, unsubscribe }
4245
}
@@ -197,18 +200,17 @@ module.exports = function createTests(createHistory, name, reset = defaultReset)
197200
history.push('/foo')
198201

199202
const middleware = syncHistory(history)
200-
unsubscribe = middleware.unsubscribe
201-
202-
const finalCreateStore = compose(
203+
store = createStore(combineReducers({
204+
routing: routeReducer
205+
}), compose(
203206
applyMiddleware(middleware),
204207
instrument()
205-
)(createStore)
206-
store = finalCreateStore(combineReducers({
207-
routing: routeReducer
208-
}))
208+
))
209209
devToolsStore = store.liftedStore
210-
211-
middleware.listenForReplays(store)
210+
unsubscribe = middleware.syncWith(store, {
211+
stateToUrl: true,
212+
urlToState: true
213+
})
212214
})
213215

214216
afterEach(() => {
@@ -273,11 +275,103 @@ module.exports = function createTests(createHistory, name, reset = defaultReset)
273275
})
274276
})
275277

278+
describe('initialState', () => {
279+
it('does not respect initialState when syncing url to state', () => {
280+
let synced = createSyncedHistoryAndStore(createHistory, {
281+
urlToState: true
282+
}, {
283+
routing: {
284+
location: {
285+
pathname: '/init',
286+
search: '',
287+
hash: '',
288+
state: null,
289+
action: 'PUSH',
290+
key: 'abcde'
291+
}
292+
}
293+
})
294+
295+
let history = synced.history
296+
let unsubscribe = synced.unsubscribe
297+
298+
let currentPath
299+
const historyUnsubscribe = history.listen(location => {
300+
currentPath = location.pathname
301+
})
302+
303+
expect(currentPath).toEqual('/')
304+
historyUnsubscribe()
305+
unsubscribe()
306+
})
307+
308+
it('respects initialState when syncing state to url', () => {
309+
let synced = createSyncedHistoryAndStore(createHistory, {
310+
stateToUrl: true
311+
}, {
312+
routing: {
313+
location: {
314+
pathname: '/init',
315+
search: '',
316+
hash: '',
317+
state: null,
318+
action: 'PUSH',
319+
key: 'abcde'
320+
}
321+
}
322+
})
323+
324+
let history = synced.history
325+
let unsubscribe = synced.unsubscribe
326+
327+
let currentPath
328+
const historyUnsubscribe = history.listen(location => {
329+
currentPath = location.pathname
330+
})
331+
332+
expect(currentPath).toEqual('/init')
333+
historyUnsubscribe()
334+
unsubscribe()
335+
})
336+
337+
it('respects initialState when syncing both ways', () => {
338+
let synced = createSyncedHistoryAndStore(createHistory, {
339+
stateToUrl: true,
340+
urlToState: true
341+
}, {
342+
routing: {
343+
location: {
344+
pathname: '/init',
345+
search: '',
346+
hash: '',
347+
state: null,
348+
action: 'PUSH',
349+
key: 'abcde'
350+
}
351+
}
352+
})
353+
354+
let history = synced.history
355+
let unsubscribe = synced.unsubscribe
356+
357+
let currentPath
358+
const historyUnsubscribe = history.listen(location => {
359+
currentPath = location.pathname
360+
})
361+
362+
expect(currentPath).toEqual('/init')
363+
historyUnsubscribe()
364+
unsubscribe()
365+
})
366+
})
367+
276368
describe('syncReduxAndRouter', () => {
277369
let history, store, unsubscribe
278370

279371
beforeEach(() => {
280-
let synced = createSyncedHistoryAndStore(createHistory)
372+
let synced = createSyncedHistoryAndStore(createHistory, {
373+
urlToState: true
374+
})
281375
history = synced.history
282376
store = synced.store
283377
unsubscribe = synced.unsubscribe
@@ -545,7 +639,9 @@ module.exports = function createTests(createHistory, name, reset = defaultReset)
545639
let history, store, unsubscribe
546640

547641
beforeEach(() => {
548-
const synced = createSyncedHistoryAndStore(useQueries(createHistory))
642+
const synced = createSyncedHistoryAndStore(useQueries(createHistory), {
643+
urlToState: true
644+
})
549645
history = synced.history
550646
store = synced.store
551647
unsubscribe = synced.unsubscribe
@@ -583,7 +679,8 @@ module.exports = function createTests(createHistory, name, reset = defaultReset)
583679

584680
beforeEach(() => {
585681
const synced = createSyncedHistoryAndStore(
586-
() => useBasename(createHistory)({ basename: '/foobar' })
682+
() => useBasename(createHistory)({ basename: '/foobar' }),
683+
{ urlToState: true }
587684
)
588685
history = synced.history
589686
store = synced.store

0 commit comments

Comments
 (0)