Skip to content

Commit bb7d8ee

Browse files
committed
use subscriptions to access location from context
1 parent a603eaa commit bb7d8ee

File tree

8 files changed

+111
-109
lines changed

8 files changed

+111
-109
lines changed

modules/Link.js

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { PropTypes } from 'react'
2+
import { LocationSubscriber } from './locationEmission'
23
import {
34
location as locationType,
45
routerContext as routerContextType
@@ -41,8 +42,7 @@ class Link extends React.Component {
4142
}
4243

4344
static contextTypes = {
44-
router: routerContextType, // TODO: This should be required, lazy testers be damned
45-
location: locationType // TODO: This should also be required
45+
router: routerContextType
4646
}
4747

4848
handleClick = (event) => {
@@ -65,53 +65,59 @@ class Link extends React.Component {
6565
}
6666

6767
render() {
68-
const { router } = this.context
69-
const {
70-
to,
71-
style,
72-
activeStyle,
73-
className,
74-
activeClassName,
75-
location,
76-
isActive: getIsActive,
77-
activeOnlyWhenExact, // eslint-disable-line
78-
...rest
79-
} = this.props
80-
81-
const currentLocation = location || this.context.location
82-
83-
const isActive = getIsActive(
84-
currentLocation,
85-
createLocationDescriptor(to),
86-
this.props
87-
)
88-
89-
// If children is a function, we are using a Function as Children Component
90-
// so useful values will be passed down to the children function.
91-
if (typeof rest.children == 'function') {
92-
return rest.children({
93-
isActive,
94-
location,
95-
href: router ? router.createHref(to) : to,
96-
onClick: this.handleClick,
97-
transition: this.handleTransition
98-
})
99-
}
100-
101-
// Maybe we should use <Match> here? Not sure how the custom `isActive`
102-
// prop would shake out, also, this check happens a LOT so maybe its good
103-
// to optimize here w/ a faster isActive check, so we'd need to bench mark
104-
// any attempt at changing to use <Match>
10568
return (
106-
<a
107-
{...rest}
108-
href={router ? router.createHref(to) : to}
109-
onClick={this.handleClick}
110-
style={isActive ? { ...style, ...activeStyle } : style }
111-
className={isActive ?
112-
[ className, activeClassName ].join(' ').trim() : className
113-
}
114-
/>
69+
<LocationSubscriber>
70+
{(contextLocation) => {
71+
const { router } = this.context
72+
const {
73+
to,
74+
style,
75+
activeStyle,
76+
className,
77+
activeClassName,
78+
location:propLocation,
79+
isActive: getIsActive,
80+
activeOnlyWhenExact, // eslint-disable-line
81+
...rest
82+
} = this.props
83+
84+
const currentLocation = propLocation || contextLocation
85+
86+
const isActive = getIsActive(
87+
currentLocation,
88+
createLocationDescriptor(to),
89+
this.props
90+
)
91+
92+
// If children is a function, we are using a Function as Children Component
93+
// so useful values will be passed down to the children function.
94+
if (typeof rest.children == 'function') {
95+
return rest.children({
96+
isActive,
97+
currentLocation,
98+
href: router ? router.createHref(to) : to,
99+
onClick: this.handleClick,
100+
transition: this.handleTransition
101+
})
102+
}
103+
104+
// Maybe we should use <Match> here? Not sure how the custom `isActive`
105+
// prop would shake out, also, this check happens a LOT so maybe its good
106+
// to optimize here w/ a faster isActive check, so we'd need to benchmark
107+
// any attempt at changing to use <Match>
108+
return (
109+
<a
110+
{...rest}
111+
href={router ? router.createHref(to) : to}
112+
onClick={this.handleClick}
113+
style={isActive ? { ...style, ...activeStyle } : style }
114+
className={isActive ?
115+
[ className, activeClassName ].join(' ').trim() : className
116+
}
117+
/>
118+
)
119+
}}
120+
</LocationSubscriber>
115121
)
116122
}
117123
}

modules/Match.js

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, { PropTypes } from 'react'
22
import MatchProvider from './MatchProvider'
33
import matchPattern from './matchPattern'
4+
import { LocationSubscriber } from './locationEmission'
45

56
class RegisterMatch extends React.Component {
67
static propTypes = {
@@ -78,28 +79,33 @@ class Match extends React.Component {
7879
}
7980

8081
render() {
81-
const { children, render, component:Component,
82-
pattern, location, exactly } = this.props
83-
const { location:locationContext, match:matchContext } = this.context
84-
const loc = location || locationContext
85-
const parent = matchContext && matchContext.parent
86-
const match = matchPattern(pattern, loc, exactly, parent)
87-
const props = { ...match, location: loc, pattern }
88-
8982
return (
90-
<RegisterMatch match={match}>
91-
<MatchProvider match={match}>
92-
{children ? (
93-
children({ matched: !!match, ...props })
94-
) : match ? (
95-
render ? (
96-
render(props)
97-
) : (
98-
<Component {...props}/>
99-
)
100-
) : null}
101-
</MatchProvider>
102-
</RegisterMatch>
83+
<LocationSubscriber>
84+
{(locationContext) => {
85+
const { children, render, component:Component,
86+
pattern, location, exactly } = this.props
87+
const { match:matchContext } = this.context
88+
const loc = location || locationContext
89+
const parent = matchContext && matchContext.parent
90+
const match = matchPattern(pattern, loc, exactly, parent)
91+
const props = { ...match, location: loc, pattern }
92+
return (
93+
<RegisterMatch match={match}>
94+
<MatchProvider match={match}>
95+
{children ? (
96+
children({ matched: !!match, ...props })
97+
) : match ? (
98+
render ? (
99+
render(props)
100+
) : (
101+
<Component {...props}/>
102+
)
103+
) : null}
104+
</MatchProvider>
105+
</RegisterMatch>
106+
)
107+
}}
108+
</LocationSubscriber>
103109
)
104110
}
105111
}

modules/StaticRouter.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import React, { PropTypes } from 'react'
22
import { stringify, parse as parseQueryString } from 'query-string'
33
import MatchProvider from './MatchProvider'
4+
import { LocationEmitter } from './locationEmission'
45
import {
56
locationsAreEqual,
67
createRouterLocation,
78
createRouterPath
89
} from './LocationUtils'
910
import {
1011
action as actionType,
11-
location as locationType,
1212
routerContext as routerContextType
1313
} from './PropTypes'
1414

@@ -41,8 +41,7 @@ class StaticRouter extends React.Component {
4141
}
4242

4343
static childContextTypes = {
44-
router: routerContextType.isRequired,
45-
location: locationType.isRequired // TODO: Remove location state from context
44+
router: routerContextType.isRequired
4645
}
4746

4847
createLocation(location) {
@@ -82,7 +81,6 @@ class StaticRouter extends React.Component {
8281

8382
getChildContext() {
8483
return {
85-
location: this.state.location, // TODO: Remove location state from context
8684
router: this.getRouterContext()
8785
}
8886
}
@@ -109,13 +107,15 @@ class StaticRouter extends React.Component {
109107
const { action, children } = this.props
110108

111109
return (
112-
<MatchProvider>
113-
{typeof children === 'function' ? (
114-
children({ action, location, router: this.getRouterContext() })
115-
) : (
116-
React.Children.only(children)
117-
)}
118-
</MatchProvider>
110+
<LocationEmitter value={location}>
111+
<MatchProvider>
112+
{typeof children === 'function' ? (
113+
children({ action, location, router: this.getRouterContext() })
114+
) : (
115+
React.Children.only(children)
116+
)}
117+
</MatchProvider>
118+
</LocationEmitter>
119119
)
120120
}
121121
}

modules/__tests__/Link-test.js

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import expect from 'expect'
22
import React, { PropTypes } from 'react'
33
import Link from '../Link'
44
import { render } from 'react-dom'
5+
import { LocationEmitter } from '../locationEmission'
56

67
describe('Link', () => {
78

@@ -252,31 +253,17 @@ describe('Link', () => {
252253
})
253254

254255
describe('when rendered in context of a location', () => {
255-
const PATHNAME = '/PATHNAME'
256-
257-
class TestRouterContext extends React.Component {
258-
static childContextTypes = { location: PropTypes.object }
259-
getChildContext() {
260-
return {
261-
location: {
262-
pathname: PATHNAME,
263-
search: '',
264-
hash: ''
265-
}
266-
}
267-
}
268-
render() { return this.props.children }
269-
}
270-
271256
it('uses the location from context', () => {
257+
const PATHNAME = '/PATHNAME'
272258
const div = document.createElement('div')
259+
const location = { pathname: PATHNAME, search: '', hash: '' }
273260
render((
274-
<TestRouterContext>
261+
<LocationEmitter value={location}>
275262
<Link
276263
to={PATHNAME}
277264
activeClassName="active"
278265
/>
279-
</TestRouterContext>
266+
</LocationEmitter>
280267
), div)
281268
const a = div.querySelector('a')
282269
expect(a.className).toEqual('active')

modules/__tests__/Match-test.js

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, { PropTypes } from 'react'
33
import Match from '../Match'
44
import { renderToString } from 'react-dom/server'
55
import { render } from 'react-dom'
6+
import { LocationEmitter } from '../locationEmission'
67

78
describe('Match', () => {
89
const TEXT = 'TEXT'
@@ -272,21 +273,15 @@ describe('Match', () => {
272273
})
273274

274275
describe('when rendered in context of a location', () => {
275-
class LocationProvider extends React.Component {
276-
static childContextTypes = { location: PropTypes.object }
277-
getChildContext = () => ({ location: this.props.location })
278-
render = () => this.props.children
279-
}
280-
281276
it('matches the location from context', () => {
282277
const TEXT = 'TEXT'
283278
const location = { pathname: '/', state: { test: TEXT } }
284279
const html = renderToString(
285-
<LocationProvider location={location}>
280+
<LocationEmitter value={location}>
286281
<Match pattern="/" render={({ location }) => (
287282
<div>{location.state.test}</div>
288283
)}/>
289-
</LocationProvider>
284+
</LocationEmitter>
290285
)
291286
expect(html).toContain(TEXT)
292287
})
@@ -298,11 +293,11 @@ describe('Match', () => {
298293
const contextLoc = { pathname: '/', state: { test: CONTEXT } }
299294
const propsLoc = { pathname: '/', state: { test: PROP } }
300295
const html = renderToString(
301-
<LocationProvider location={contextLoc}>
296+
<LocationEmitter value={location}>
302297
<Match location={propsLoc} pattern="/" render={({ location }) => (
303298
<div>{location.state.test}</div>
304299
)}/>
305-
</LocationProvider>
300+
</LocationEmitter>
306301
)
307302
expect(html).toNotContain(CONTEXT)
308303
expect(html).toContain(PROP)

modules/__tests__/MemoryRouter-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import React from 'react'
44
import MemoryRouter from '../MemoryRouter'
55
import Match from '../Match'
66

7-
describe('MemoryHistory', () => {
7+
describe('MemoryRouter', () => {
88

99
it('works', () => {
1010
const markup = renderToString(

modules/locationEmission.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { createContextEmitter, createContextSubscriber } from 'react-context-emission'
2+
3+
const NAME = 'location'
4+
const LocationEmitter = createContextEmitter(NAME)
5+
const LocationSubscriber = createContextSubscriber(NAME)
6+
7+
export { LocationEmitter, LocationSubscriber }

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"dependencies": {
2929
"path-to-regexp": "^1.5.3",
3030
"query-string": "4.2.3",
31+
"react-context-emission": "^2.1.0",
3132
"react-history": "^0.14.0"
3233
},
3334
"peerDependencies": {

0 commit comments

Comments
 (0)