Skip to content

remove subscriptions #4598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 22 additions & 33 deletions packages/react-router/modules/Route.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import React, { PropTypes } from 'react'
import warning from 'warning'
import matchPath from './matchPath'

Copy link
Member Author

@ryanflorence ryanflorence Feb 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved computeMatch cause it felt cleaner as an instance method, only had to check context.router.location v. props.location in one spot. Also, it's our doImperativeStuff method of this component. We do it initially, and then when we get new props.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually like the method up here, and I like that it's just 1 line up here instead of 4 down below. No need to create the function for every instance. Once will do :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaScript prototypes use only one function per instance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I was having to do the same logic to figure out the arguments to pass to computeMatch, so complexity was just moving up and repeated, now its only in one place.

const computeMatch = (router, { location, computedMatch, path, exact, strict }) =>
computedMatch || matchPath((location || router.location).pathname, { path, exact, strict })

/**
* The public API for matching a single path and rendering.
*/
class Route extends React.Component {
static contextTypes = {
router: PropTypes.shape({
listen: PropTypes.func.isRequired
}).isRequired
router: PropTypes.object.isRequired
}

static childContextTypes = {
router: PropTypes.object.isRequired
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like keeping childContextTypes right next to getChildContext so I can see the two together easily.

}

static propTypes = {
Expand All @@ -29,37 +29,22 @@ class Route extends React.Component {
location: PropTypes.object
}

static childContextTypes = {
router: PropTypes.object.isRequired
state = {
match: this.computeMatch(this.props)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe that match should live on context.router for the sake of nested <Link>s and <Route>s that need to be relative. Can we keep it there for now and just remove the subscriptions in this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep

}

getChildContext() {
return {
router: this.router
router: {
...this.context.router,
match: this.state.match
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^ added match back in

}
}

componentWillMount() {
const parentRouter = this.context.router

this.router = {
...parentRouter,
match: computeMatch(parentRouter, this.props)
}

// Start listening here so we can <Redirect> on the initial render.
this.unlisten = parentRouter.listen(() => {
Object.assign(this.router, parentRouter, {
match: computeMatch(parentRouter, this.props)
})

this.forceUpdate()
})
}

componentWillReceiveProps(nextProps) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to remove this.router, we should at least have a few tests that ensure correct behavior. Also, Redux users are only 1 group that uses shouldComponentUpdate. Anyone else who's using it may still benefit from us mutating context.router.

Object.assign(this.router, {
match: computeMatch(this.router, nextProps)
this.setState({
match: this.computeMatch(nextProps)
})

warning(
Expand All @@ -72,19 +57,23 @@ class Route extends React.Component {
)
}

componentWillUnmount() {
this.unlisten()
computeMatch(props) {
const { location, computedMatch, path, exact, strict } = props
const { router } = this.context
const pathname = (location || router.location).pathname
return computedMatch || matchPath(pathname, { path, exact, strict })
}

render() {
const { children, component, render } = this.props
const props = { ...this.router }
const { match } = this.state
const props = { ...this.context.router, match }

return (
component ? ( // component prop gets first priority, only called if there's a match
props.match ? React.createElement(component, props) : null
match ? React.createElement(component, props) : null
) : render ? ( // render prop is next, only called if there's a match
props.match ? render(props) : null
match ? render(props) : null
) : children ? ( // children come last, always called
typeof children === 'function' ? (
children(props)
Expand Down
23 changes: 7 additions & 16 deletions packages/react-router/modules/Router.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Router extends React.Component {

getChildContext() {
return {
router: this.router
router: this.props.history
}
}

Expand All @@ -28,24 +28,15 @@ class Router extends React.Component {
'A <Router> may have only one child element'
)

this.router = {
...history,
match: {
path: '/',
url: '/',
params: {},
isExact: history.location.pathname === '/'
}
}

history.listen(() => {
Object.assign(this.router, history)
Object.assign(this.router.match, {
isExact: history.location.pathname === '/'
})
this.unlisten = history.listen(() => {
this.forceUpdate()
})
}

componentWillUnmount() {
this.unlisten()
}

render() {
const { children } = this.props
return children ? React.Children.only(children) : null
Expand Down
28 changes: 2 additions & 26 deletions packages/react-router/modules/Switch.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,14 @@ import matchPath from './matchPath'
*/
class Switch extends React.Component {
static contextTypes = {
router: PropTypes.shape({
listen: PropTypes.func.isRequired
}).isRequired
router: PropTypes.object.isRequired
}

static propTypes = {
children: PropTypes.node,
location: PropTypes.object
}

state = {
location: this.props.location || this.context.router.location
}

componentWillMount() {
if (!this.props.location) {
const { router } = this.context

// Start listening here so we can <Redirect> on the initial render.
this.unlisten = router.listen(() => {
this.setState({
location: router.location
})
})
}
}

componentWillReceiveProps(nextProps) {
warning(
!(nextProps.location && !this.props.location),
Expand All @@ -46,14 +27,9 @@ class Switch extends React.Component {
)
}

componentWillUnmount() {
if (this.unlisten)
this.unlisten()
}

render() {
const { children } = this.props
const { location } = this.state
const location = this.props.location || this.context.router.location

let match, child
React.Children.forEach(children, element => {
Expand Down
87 changes: 5 additions & 82 deletions packages/react-router/modules/__tests__/Router-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import Router from '../Router'
import ReactDOM from 'react-dom'
import createHistory from 'history/createMemoryHistory'

const fakeHistory = { listen: () => () => {} }

describe('A <Router>', () => {
const node = document.createElement('div')

Expand All @@ -15,7 +17,7 @@ describe('A <Router>', () => {
it('throws an error explaining a Router can only have one child', () => {
expect(() => {
ReactDOM.render(
<Router history={createHistory()}>
<Router history={fakeHistory}>
<p>Foo</p>
<p>Bar</p>
</Router>,
Expand All @@ -29,7 +31,7 @@ describe('A <Router>', () => {
it('does not throw an error', () => {
expect(() => {
ReactDOM.render(
<Router history={createHistory()}>
<Router history={fakeHistory}>
<p>Bar</p>
</Router>,
node
Expand All @@ -42,89 +44,10 @@ describe('A <Router>', () => {
it('does not throw an error', () => {
expect(() => {
ReactDOM.render(
<Router history={createHistory()} />,
<Router history={fakeHistory} />,
node
)
}).toNotThrow()
})
})

describe('context.router', () => {
let rootContext
const ContextChecker = (props, context) => {
rootContext = context.router
return null
}
ContextChecker.contextTypes = { router: React.PropTypes.object }

afterEach(() => {
rootContext = undefined
})

it('sets a root match', () => {
const history = createHistory({ initialEntries: ['/'] })
ReactDOM.render(
<Router history={history}>
<ContextChecker />
</Router>,
node
)
expect(rootContext.match).toEqual({
path: '/',
url: '/',
params: {},
isExact: true
})
})

it('spreads the history object\'s properties', () => {
const history = createHistory()
ReactDOM.render(
<Router history={history}>
<ContextChecker />
</Router>,
node
)

Object.keys(history).forEach(key => {
expect(rootContext[key]).toEqual(history[key])
})
})

it('updates history properties upon navigation', () => {
const history = createHistory()
ReactDOM.render(
<Router history={history}>
<ContextChecker />
</Router>,
node
)
expect(rootContext.length).toBe(1)

const newLocation = { pathname: '/new' }
history.push(newLocation)

Object.keys(newLocation).forEach(key => {
expect(rootContext.location[key]).toEqual(newLocation[key])
})
expect(rootContext.action).toBe('PUSH')
expect(rootContext.length).toBe(2)
})

it('updates match.isExact upon navigation', () => {
const history = createHistory({ initialEntries: ['/'] })
ReactDOM.render(
<Router history={history}>
<ContextChecker />
</Router>,
node
)
expect(rootContext.match.isExact).toBe(true)

const newLocation = { pathname: '/new' }
history.push(newLocation)

expect(rootContext.match.isExact).toBe(false)
})
})
})
57 changes: 0 additions & 57 deletions packages/react-router/modules/__tests__/withRouter-test.js

This file was deleted.

1 change: 0 additions & 1 deletion packages/react-router/modules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ export Router from './Router'
export StaticRouter from './StaticRouter'
export Switch from './Switch'
export matchPath from './matchPath'
export withRouter from './withRouter'
Loading