diff --git a/src/ConnectedRouter.js b/src/ConnectedRouter.js index a3032038..c1a1da5c 100644 --- a/src/ConnectedRouter.js +++ b/src/ConnectedRouter.js @@ -1,11 +1,12 @@ -import React, { Component } from 'react' +import React, {PureComponent} from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import { Router } from 'react-router' import { onLocationChanged } from './actions' +import createSelectors from "./selectors" const createConnectedRouter = (structure) => { - const { getIn, toJS } = structure + const { getLocation } = createSelectors(structure) /* * ConnectedRouter listens to a history object passed from props. * When history is changed, it dispatches action to redux store. @@ -13,20 +14,26 @@ const createConnectedRouter = (structure) => { * This creates uni-directional flow from history->store->router->components. */ - class ConnectedRouter extends Component { + class ConnectedRouter extends PureComponent { + + constructor(props, context) { + super(props) + this.passedContext = context + } + componentDidMount() { - const { store, history, onLocationChanged } = this.props + const { history, onLocationChanged } = this.props this.inTimeTravelling = false - // Subscribe to store changes to check if we are in time travelling - this.unsubscribe = store.subscribe(() => { + // Subscribe to store changes + this.unsubscribe = this.passedContext.store.subscribe(() => { // Extract store's location const { pathname: pathnameInStore, search: searchInStore, hash: hashInStore, - } = toJS(getIn(store.getState(), ['router', 'location'])) + } = getLocation(this.passedContext.store.getState()) // Extract history's location const { pathname: pathnameInHistory, @@ -89,26 +96,16 @@ 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(state, ['router', 'action']), - location: getIn(state, ['router', 'location']), - }) - const mapDispatchToProps = dispatch => ({ onLocationChanged: (location, action) => dispatch(onLocationChanged(location, action)) }) - return connect(mapStateToProps, mapDispatchToProps)(ConnectedRouter) + return connect(null, mapDispatchToProps)(ConnectedRouter) } export default createConnectedRouter diff --git a/test/ConnectedRouter.test.js b/test/ConnectedRouter.test.js index f722f67a..8662c2fb 100644 --- a/test/ConnectedRouter.test.js +++ b/test/ConnectedRouter.test.js @@ -2,7 +2,7 @@ import 'raf/polyfill' import React, { Children, Component } from 'react' import PropTypes from 'prop-types' import configureStore from 'redux-mock-store' -import { createStore, combineReducers } from 'redux' +import {createStore, combineReducers, compose, applyMiddleware} from 'redux' import { ActionCreators, instrument } from 'redux-devtools' import Enzyme from 'enzyme' import Adapter from 'enzyme-adapter-react-16' @@ -14,6 +14,7 @@ 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 routerMiddleware from "../src/middleware" Enzyme.configure({ adapter: new Adapter() }) @@ -151,6 +152,118 @@ describe('ConnectedRouter', () => { expect(onLocationChangedSpy.mock.calls).toHaveLength(1) }) + + 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) + }) + + 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 seamless immutable structure', () => { @@ -198,6 +311,62 @@ describe('ConnectedRouter', () => { expect(onLocationChangedSpy.mock.calls).toHaveLength(1) }) + + 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('Redux DevTools', () => {