Skip to content

Commit f771154

Browse files
authored
Merge pull request #201 from v12/rr-v4
React Router v4 support
2 parents a7de1be + 32f9e44 commit f771154

11 files changed

+192
-198
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@
7373
"lodash": "^4.14.0",
7474
"mocha": "^2.5.3",
7575
"react": "^15.2.1",
76+
"react-addons-test-utils": "^15.4.2",
7677
"react-bootstrap": "^0.30.0",
7778
"react-dom": "^15.2.1",
78-
"react-router": "^2.6.0",
79+
"react-router-dom": "^4.0.0",
7980
"release-script": "^1.0.2",
8081
"rimraf": "^2.5.4",
8182
"shelljs": "^0.7.2",

src/IndexLinkContainer.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import LinkContainer from './LinkContainer';
77
export default class IndexLinkContainer extends React.Component {
88
render() {
99
return (
10-
<LinkContainer {...this.props} onlyActiveOnIndex />
10+
<LinkContainer {...this.props} exact />
1111
);
1212
}
1313
}

src/LinkContainer.js

+90-85
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,46 @@
1-
// This is largely taken from react-router/lib/Link.
2-
3-
import React from 'react';
4-
5-
function isLeftClickEvent(event) {
6-
return event.button === 0;
7-
}
8-
9-
function isModifiedEvent(event) {
10-
return !!(
11-
event.metaKey ||
12-
event.altKey ||
13-
event.ctrlKey ||
14-
event.shiftKey
15-
);
16-
}
17-
18-
function createLocationDescriptor(to, query, hash, state) {
19-
if (query || hash || state) {
20-
return { pathname: to, query, hash, state };
21-
}
22-
23-
return to;
24-
}
25-
26-
const propTypes = {
27-
onlyActiveOnIndex: React.PropTypes.bool.isRequired,
28-
to: React.PropTypes.oneOfType([
29-
React.PropTypes.string,
30-
React.PropTypes.object,
31-
]).isRequired,
32-
query: React.PropTypes.string,
33-
hash: React.PropTypes.string,
34-
state: React.PropTypes.object,
35-
action: React.PropTypes.oneOf([
36-
'push',
37-
'replace',
38-
]).isRequired,
39-
onClick: React.PropTypes.func,
40-
active: React.PropTypes.bool,
41-
target: React.PropTypes.string,
42-
children: React.PropTypes.node.isRequired,
43-
};
1+
import React, { Component, PropTypes } from 'react';
2+
import { Route } from 'react-router-dom';
3+
4+
const isModifiedEvent = (event) =>
5+
!!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
6+
7+
export default class LinkContainer extends Component {
8+
static contextTypes = {
9+
router: PropTypes.shape({
10+
history: PropTypes.shape({
11+
push: PropTypes.func.isRequired,
12+
replace: PropTypes.func.isRequired,
13+
createHref: PropTypes.func.isRequired,
14+
}).isRequired,
15+
}).isRequired,
16+
};
4417

45-
const contextTypes = {
46-
router: React.PropTypes.object,
47-
};
18+
static propTypes = {
19+
children: PropTypes.element.isRequired,
20+
onClick: PropTypes.func,
21+
replace: PropTypes.bool,
22+
to: PropTypes.oneOfType([
23+
PropTypes.string,
24+
PropTypes.object,
25+
]).isRequired,
26+
exact: PropTypes.bool,
27+
strict: PropTypes.bool,
28+
className: PropTypes.string,
29+
activeClassName: PropTypes.string,
30+
style: PropTypes.object,
31+
activeStyle: PropTypes.object,
32+
isActive: PropTypes.func,
33+
};
4834

49-
const defaultProps = {
50-
onlyActiveOnIndex: false,
51-
action: 'push',
52-
};
35+
static defaultProps = {
36+
replace: false,
37+
exact: false,
38+
strict: false,
39+
activeClassName: 'active',
40+
};
5341

54-
class LinkContainer extends React.Component {
55-
onClick = (event) => {
56-
const {
57-
to, query, hash, state, children, onClick, target, action,
58-
} = this.props;
42+
handleClick = (event) => {
43+
const { children, onClick } = this.props;
5944

6045
if (children.props.onClick) {
6146
children.props.onClick(event);
@@ -66,42 +51,62 @@ class LinkContainer extends React.Component {
6651
}
6752

6853
if (
69-
target ||
70-
event.defaultPrevented ||
71-
isModifiedEvent(event) ||
72-
!isLeftClickEvent(event)
54+
!event.defaultPrevented && // onClick prevented default
55+
event.button === 0 && // ignore right clicks
56+
!isModifiedEvent(event) // ignore clicks with modifier keys
7357
) {
74-
return;
75-
}
58+
event.preventDefault();
7659

77-
event.preventDefault();
60+
const { history } = this.context.router;
61+
const { replace, to } = this.props;
7862

79-
this.context.router[action](
80-
createLocationDescriptor(to, query, hash, state)
81-
);
82-
};
63+
if (replace) {
64+
history.replace(to);
65+
} else {
66+
history.push(to);
67+
}
68+
}
69+
}
8370

8471
render() {
85-
const { router } = this.context;
86-
const { onlyActiveOnIndex, to, children, ...props } = this.props;
87-
88-
props.onClick = this.onClick;
89-
90-
// Ignore if rendered outside Router context; simplifies unit testing.
91-
if (router) {
92-
props.href = router.createHref(to);
72+
const {
73+
children,
74+
replace, // eslint-disable-line no-unused-vars
75+
to,
76+
exact,
77+
strict,
78+
activeClassName,
79+
className,
80+
activeStyle,
81+
style,
82+
isActive: getIsActive,
83+
...props,
84+
} = this.props;
9385

94-
if (props.active == null) {
95-
props.active = router.isActive(to, onlyActiveOnIndex);
96-
}
97-
}
86+
const href = this.context.router.history.createHref(
87+
typeof to === 'string' ? { pathname: to } : to
88+
);
9889

99-
return React.cloneElement(React.Children.only(children), props);
90+
return (
91+
<Route
92+
path={typeof to === 'object' ? to.pathname : to}
93+
exact={exact}
94+
strict={strict}
95+
children={({ location, match }) => {
96+
const isActive = !!(getIsActive ? getIsActive(match, location) : match);
97+
98+
return React.cloneElement(
99+
React.Children.only(children),
100+
{
101+
...props,
102+
className: isActive ? [className, activeClassName].join(' ') : className,
103+
style: isActive ? { ...style, ...activeStyle } : style,
104+
href,
105+
onClick: this.handleClick,
106+
}
107+
);
108+
}}
109+
/>
110+
);
100111
}
101112
}
102-
103-
LinkContainer.propTypes = propTypes;
104-
LinkContainer.contextTypes = contextTypes;
105-
LinkContainer.defaultProps = defaultProps;
106-
107-
export default LinkContainer;

test/IndexLinkContainer.spec.js

+15-16
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React from 'react';
2-
import ReactTestUtils from 'react/lib/ReactTestUtils';
2+
import ReactTestUtils from 'react-addons-test-utils';
33
import * as ReactBootstrap from 'react-bootstrap';
4-
import ReactDOM from 'react-dom';
5-
import { createMemoryHistory, IndexRoute, Route, Router } from 'react-router';
4+
import { findDOMNode } from 'react-dom';
5+
import { Route, MemoryRouter as Router } from 'react-router-dom';
66

77
import IndexLinkContainer from '../src/IndexLinkContainer';
88

@@ -19,25 +19,24 @@ describe('IndexLinkContainer', () => {
1919
describe('active state', () => {
2020
function renderComponent(location) {
2121
const router = ReactTestUtils.renderIntoDocument(
22-
<Router history={createMemoryHistory(location)}>
23-
<Route
24-
path="/"
25-
component={() => (
26-
<IndexLinkContainer to="/">
27-
<Component>Root</Component>
28-
</IndexLinkContainer>
29-
)}
30-
>
31-
<IndexRoute />
32-
<Route path="foo" />
33-
</Route>
22+
<Router initialEntries={[location]}>
23+
<div>
24+
<Route
25+
path="/"
26+
render={() => (
27+
<IndexLinkContainer to="/">
28+
<Component>Root</Component>
29+
</IndexLinkContainer>
30+
)}
31+
/>
32+
</div>
3433
</Router>
3534
);
3635

3736
const component = ReactTestUtils.findRenderedComponentWithType(
3837
router, Component
3938
);
40-
return ReactDOM.findDOMNode(component);
39+
return findDOMNode(component);
4140
}
4241

4342
it('should be active on the index route', () => {

0 commit comments

Comments
 (0)