diff --git a/src/ConnectedRouter.js b/src/ConnectedRouter.js index 90a39863..67d15635 100644 --- a/src/ConnectedRouter.js +++ b/src/ConnectedRouter.js @@ -1,4 +1,4 @@ -import React, { Component } from 'react' +import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import { connect, ReactReduxContext } from 'react-redux' import { Router } from 'react-router' @@ -6,8 +6,7 @@ import { onLocationChanged } from './actions' import createSelectors from './selectors' const createConnectedRouter = (structure) => { - const { getIn } = structure - const { getRouter, getLocation } = createSelectors(structure) + const { getLocation } = createSelectors(structure) /* * ConnectedRouter listens to a history object passed from props. * When history is changed, it dispatches action to redux store. @@ -15,7 +14,7 @@ const createConnectedRouter = (structure) => { * This creates uni-directional flow from history->store->router->components. */ - class ConnectedRouter extends Component { + class ConnectedRouter extends PureComponent { constructor(props) { super(props) @@ -90,21 +89,11 @@ const createConnectedRouter = (structure) => { location: PropTypes.object.isRequired, push: PropTypes.func.isRequired, }).isRequired, - location: PropTypes.oneOfType([ - PropTypes.object, - PropTypes.string, - ]).isRequired, - action: PropTypes.string.isRequired, basename: PropTypes.string, children: PropTypes.oneOfType([ PropTypes.func, PropTypes.node ]), onLocationChanged: PropTypes.func.isRequired, } - const mapStateToProps = state => ({ - action: getIn(getRouter(state), ['action']), - location: getIn(getRouter(state), ['location']), - }) - const mapDispatchToProps = dispatch => ({ onLocationChanged: (location, action) => dispatch(onLocationChanged(location, action)) }) @@ -127,7 +116,7 @@ const createConnectedRouter = (structure) => { context: PropTypes.object, } - return connect(mapStateToProps, mapDispatchToProps)(ConnectedRouterWithContext) + return connect(null, mapDispatchToProps)(ConnectedRouterWithContext) } export default createConnectedRouter diff --git a/test/ConnectedRouter.test.js b/test/ConnectedRouter.test.js index 6a613899..a2dabd8d 100644 --- a/test/ConnectedRouter.test.js +++ b/test/ConnectedRouter.test.js @@ -1,20 +1,19 @@ import 'raf/polyfill' -import React, { Children, Component } from 'react' -import PropTypes from 'prop-types' +import React from 'react' import configureStore from 'redux-mock-store' -import { createStore, combineReducers } from 'redux' +import { createStore, combineReducers, applyMiddleware, compose } from 'redux' import { ActionCreators, instrument } from 'redux-devtools' import Enzyme from 'enzyme' import Adapter from 'enzyme-adapter-react-16' import { createMemoryHistory } from 'history' import { Route } from 'react-router' -import { ReactReduxContext } from 'react-redux' +import { Provider } from 'react-redux' import createConnectedRouter from '../src/ConnectedRouter' import { onLocationChanged } from '../src/actions' import plainStructure from '../src/structure/plain' import immutableStructure from '../src/structure/immutable' import seamlessImmutableStructure from '../src/structure/seamless-immutable' -import { connectRouter, ConnectedRouter } from '../src' +import { connectRouter, ConnectedRouter, routerMiddleware } from '../src' Enzyme.configure({ adapter: new Adapter() }) @@ -69,11 +68,11 @@ describe('ConnectedRouter', () => { it('calls `props.onLocationChanged()` when location changes.', () => { mount( - +
Home
} />
-
+ ) expect(onLocationChangedSpy.mock.calls).toHaveLength(1) @@ -86,11 +85,11 @@ describe('ConnectedRouter', () => { it('unlistens the history object when unmounted.', () => { const wrapper = mount( - +
Home
} />
-
+ ) expect(onLocationChangedSpy.mock.calls).toHaveLength(1) @@ -109,11 +108,11 @@ describe('ConnectedRouter', () => { it('supports custom context', () => { const context = React.createContext(null) mount( - +
Home
} />
-
+ ) expect(onLocationChangedSpy.mock.calls).toHaveLength(1) @@ -123,7 +122,63 @@ describe('ConnectedRouter', () => { expect(onLocationChangedSpy.mock.calls).toHaveLength(3) }) - }) + + it('only renders one time when mounted', () => { + let renderCount = 0 + + const RenderCounter = () => { + renderCount++ + return null + } + + mount( + + + + + + ) + + expect(renderCount).toBe(1) + }) + + it('does not render again when non-related action is fired', () => { + // Initialize the render counter variable + let renderCount = 0 + + // Create redux store with router state + store = createStore( + combineReducers({ + incrementReducer: (state = 0, action = {}) => { + if (action.type === 'testAction') + return ++state + + return state + }, + router: connectRouter(history) + }), + compose(applyMiddleware(routerMiddleware(history))) + ) + + + const RenderCounter = () => { + renderCount++ + return null + } + + mount( + + + + + + ) + + store.dispatch({ type: 'testAction' }) + history.push('/new-location') + expect(renderCount).toBe(2) + }) + }) describe('with immutable structure', () => { let ConnectedRouter @@ -134,11 +189,11 @@ describe('ConnectedRouter', () => { it('calls `props.onLocationChanged()` when location changes.', () => { mount( - +
Home
} />
-
+ ) expect(onLocationChangedSpy.mock.calls).toHaveLength(1) @@ -151,11 +206,11 @@ describe('ConnectedRouter', () => { it('unlistens the history object when unmounted.', () => { const wrapper = mount( - +
Home
} />
-
+ ) expect(onLocationChangedSpy.mock.calls).toHaveLength(1) @@ -174,11 +229,11 @@ describe('ConnectedRouter', () => { it('supports custom context', () => { const context = React.createContext(null) mount( - +
Home
} />
-
+ ) expect(onLocationChangedSpy.mock.calls).toHaveLength(1) @@ -199,11 +254,11 @@ describe('ConnectedRouter', () => { it('calls `props.onLocationChanged()` when location changes.', () => { mount( - +
Home
} />
-
+ ) expect(onLocationChangedSpy.mock.calls).toHaveLength(1) @@ -216,11 +271,11 @@ describe('ConnectedRouter', () => { it('unlistens the history object when unmounted.', () => { const wrapper = mount( - +
Home
} />
-
+ ) expect(onLocationChangedSpy.mock.calls).toHaveLength(1) @@ -254,11 +309,11 @@ describe('ConnectedRouter', () => { it('resets to the initial url', () => { mount( - +
Test
-
+ ) let currentPath @@ -276,11 +331,11 @@ describe('ConnectedRouter', () => { it('handles toggle after history change', () => { mount( - +
Test
-
+ ) let currentPath @@ -300,36 +355,3 @@ describe('ConnectedRouter', () => { }) }) }) - -// MockProvider mocks react-redux's Provider component -class MockProvider extends Component { - constructor(props) { - super(props) - const { store } = props - this.state = { - storeState: store.getState(), - store, - } - } - render() { - const Context = this.props.context || ReactReduxContext - - return ( - - {Children.only(this.props.children)} - - ) - } -} - -const storeShape = PropTypes.shape({ - subscribe: PropTypes.func.isRequired, - dispatch: PropTypes.func.isRequired, - getState: PropTypes.func.isRequired, -}) - -MockProvider.propTypes = { - context: PropTypes.object, - store: storeShape.isRequired, - children: PropTypes.element.isRequired, -}