twreporter-react
Version:
React-Redux site for The Reporter Foundation in Taiwan
140 lines (122 loc) • 4.51 kB
JavaScript
import { LOCATION_CHANGE } from './reducer'
const defaultSelectLocationState = state => state.routing
/**
* This function synchronizes your history state with the Redux store.
* Location changes flow from history to the store. An enhanced history is
* returned with a listen method that responds to store updates for location.
*
* When this history is provided to the router, this means the location data
* will flow like this:
* history.push -> store.dispatch -> enhancedHistory.listen -> router
* This ensures that when the store state changes due to a replay or other
* event, the router will be updated appropriately and can transition to the
* correct router state.
*/
export default function syncHistoryWithStore(history, store, {
selectLocationState = defaultSelectLocationState,
adjustUrlOnReplay = true
} = {}) {
// Ensure that the reducer is mounted on the store and functioning properly.
if (typeof selectLocationState(store.getState()) === 'undefined') {
throw new Error(
'Expected the routing state to be available either as `state.routing` ' +
'or as the custom expression you can specify as `selectLocationState` ' +
'in the `syncHistoryWithStore()` options. ' +
'Ensure you have added the `routerReducer` to your store\'s ' +
'reducers via `combineReducers` or whatever method you use to isolate ' +
'your reducers.'
)
}
let initialLocation
let currentLocation
let isTimeTraveling
let unsubscribeFromStore
let unsubscribeFromHistory
// What does the store say about current location?
const getLocationInStore = (useInitialIfEmpty) => {
const locationState = selectLocationState(store.getState())
return locationState.locationBeforeTransitions ||
(useInitialIfEmpty ? initialLocation : undefined)
}
// If the store is replayed, update the URL in the browser to match.
if (adjustUrlOnReplay) {
const handleStoreChange = () => {
const locationInStore = getLocationInStore(true)
if (currentLocation === locationInStore) {
return
}
// Update address bar to reflect store state
isTimeTraveling = true
currentLocation = locationInStore
history.transitionTo({
...locationInStore,
action: 'PUSH'
})
isTimeTraveling = false
}
unsubscribeFromStore = store.subscribe(handleStoreChange)
handleStoreChange()
}
// Whenever location changes, dispatch an action to get it in the store
const handleLocationChange = (location) => {
// ... unless we just caused that location change
if (isTimeTraveling) {
return
}
// Remember where we are
currentLocation = location
// Are we being called for the first time?
if (!initialLocation) {
// Remember as a fallback in case state is reset
initialLocation = location
// Respect persisted location, if any
if (getLocationInStore()) {
return
}
}
// Tell the store to update by dispatching an action
store.dispatch({
type: LOCATION_CHANGE,
payload: location
})
}
unsubscribeFromHistory = history.listen(handleLocationChange)
// The enhanced history uses store as source of truth
return {
...history,
// The listeners are subscribed to the store instead of history
listen(listener) {
// Copy of last location.
let lastPublishedLocation = getLocationInStore(true)
// Keep track of whether we unsubscribed, as Redux store
// only applies changes in subscriptions on next dispatch
let unsubscribed = false
const unsubscribeFromStore = store.subscribe(() => {
const currentLocation = getLocationInStore(true)
if (currentLocation === lastPublishedLocation) {
return
}
lastPublishedLocation = currentLocation
if (!unsubscribed) {
listener(lastPublishedLocation)
}
})
// History listeners expect a synchronous call. Make the first call to the
// listener after subscribing to the store, in case the listener causes a
// location change (e.g. when it redirects)
listener(lastPublishedLocation)
// Let user unsubscribe later
return () => {
unsubscribed = true
unsubscribeFromStore()
}
},
// It also provides a way to destroy internal listeners
unsubscribe() {
if (adjustUrlOnReplay) {
unsubscribeFromStore()
}
unsubscribeFromHistory()
}
}
}