From 2bb78aae1008280a4f8181e0f6298127433376f1 Mon Sep 17 00:00:00 2001 From: Supasate Choochaisri Date: Fri, 28 Dec 2018 02:30:35 +0700 Subject: [PATCH] Prevent double-rendering on ininitilization --- src/ConnectedRouter.js | 6 ++- src/actions.js | 3 +- src/reducer.js | 7 +++- test/actions.test.js | 18 +++++++++ test/reducer.test.js | 92 +++++++++++++++++++++++++++++++++++++++++- 5 files changed, 121 insertions(+), 5 deletions(-) diff --git a/src/ConnectedRouter.js b/src/ConnectedRouter.js index 2f558770..e1cc2596 100644 --- a/src/ConnectedRouter.js +++ b/src/ConnectedRouter.js @@ -58,8 +58,10 @@ const createConnectedRouter = (structure) => { // Listen to history changes this.unlisten = history.listen(handleLocationChange) - // Dispatch a location change action for the initial location - handleLocationChange(history.location, history.action) + // 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() { diff --git a/src/actions.js b/src/actions.js index 957da2be..2f88cf01 100644 --- a/src/actions.js +++ b/src/actions.js @@ -4,11 +4,12 @@ */ export const LOCATION_CHANGE = '@@router/LOCATION_CHANGE' -export const onLocationChanged = (location, action) => ({ +export const onLocationChanged = (location, action, isFirstRendering = false) => ({ type: LOCATION_CHANGE, payload: { location, action, + isFirstRendering, } }) diff --git a/src/reducer.js b/src/reducer.js index 4db4fcc3..dad835de 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -18,7 +18,12 @@ const createConnectRouter = (structure) => { */ return (state = initialRouterState, { type, payload } = {}) => { if (type === LOCATION_CHANGE) { - return merge(state, payload) + const { location, action, isFirstRendering } = payload + // Don't update the state ref for the first rendering + // to prevent the double-rendering issue on initilization + return isFirstRendering + ? state + : merge(state, { location, action }) } return state diff --git a/test/actions.test.js b/test/actions.test.js index 020b9c03..6cbff3b7 100644 --- a/test/actions.test.js +++ b/test/actions.test.js @@ -21,6 +21,24 @@ describe('Actions', () => { hash: '', }, action: 'POP', + isFirstRendering: false, + }, + } + expect(actualAction).toEqual(expectedAction) + }) + + it('returns correct action when calling onLocationChanged() for the first rendering', () => { + const actualAction = onLocationChanged({ pathname: '/', search: '', hash: '' }, 'POP', true) + const expectedAction = { + type: LOCATION_CHANGE, + payload: { + location: { + pathname: '/', + search: '', + hash: '', + }, + action: 'POP', + isFirstRendering: true, }, } expect(actualAction).toEqual(expectedAction) diff --git a/test/reducer.test.js b/test/reducer.test.js index 2941b719..f35c1c45 100644 --- a/test/reducer.test.js +++ b/test/reducer.test.js @@ -92,6 +92,36 @@ describe('connectRouter', () => { const nextState = rootReducer(currentState, action) expect(nextState).toBe(currentState) }) + + it('does not change state ref when receiving LOCATION_CHANGE for the first rendering', () => { + const rootReducer = combineReducers({ + router: connectRouter(mockHistory) + }) + const currentState = { + router: { + location: { + pathname: '/', + search: '', + hash: '', + }, + action: 'POP', + }, + } + const action = { + type: LOCATION_CHANGE, + payload: { + location: { + pathname: '/', + search: '', + hash: '', + }, + action: 'POP', + isFirstRendering: true, + } + } + const nextState = rootReducer(currentState, action) + expect(nextState).toBe(currentState) + }) }) describe('with immutable structure', () => { @@ -143,6 +173,36 @@ describe('connectRouter', () => { }) expect(nextState).toEqual(expectedState) }) + + it('does not change state ref when receiving LOCATION_CHANGE for the first rendering', () => { + const rootReducer = combineReducers({ + router: connectRouter(mockHistory) + }) + const currentState = { + router: { + location: { + pathname: '/', + search: '', + hash: '', + }, + action: 'POP', + }, + } + const action = { + type: LOCATION_CHANGE, + payload: { + location: { + pathname: '/', + search: '', + hash: '', + }, + action: 'POP', + isFirstRendering: true, + } + } + const nextState = rootReducer(currentState, action) + expect(nextState).toBe(currentState) + }) }) describe('with seamless immutable structure', () => { @@ -194,5 +254,35 @@ describe('connectRouter', () => { } expect(nextState).toEqual(expectedState) }) + + it('does not change state ref when receiving LOCATION_CHANGE for the first rendering', () => { + const rootReducer = combineReducers({ + router: connectRouter(mockHistory) + }) + const currentState = { + router: { + location: { + pathname: '/', + search: '', + hash: '', + }, + action: 'POP', + }, + } + const action = { + type: LOCATION_CHANGE, + payload: { + location: { + pathname: '/', + search: '', + hash: '', + }, + action: 'POP', + isFirstRendering: true, + } + } + const nextState = rootReducer(currentState, action) + expect(nextState).toBe(currentState) + }) }) -}) \ No newline at end of file +})