Skip to content

Commit

Permalink
Add <Link component> prop
Browse files Browse the repository at this point in the history
See #5437
  • Loading branch information
mjackson committed Nov 8, 2018
1 parent 980bdd1 commit 3ed5482
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 60 deletions.
24 changes: 12 additions & 12 deletions packages/react-router-dom/.size-snapshot.json
@@ -1,26 +1,26 @@
{
"esm/react-router-dom.js": {
"bundled": 8022,
"minified": 4817,
"gzipped": 1612,
"bundled": 7939,
"minified": 4736,
"gzipped": 1595,
"treeshaked": {
"rollup": {
"code": 453,
"import_statements": 417
"code": 379,
"import_statements": 355
},
"webpack": {
"code": 1661
"code": 1612
}
}
},
"umd/react-router-dom.js": {
"bundled": 160055,
"minified": 57467,
"gzipped": 16536
"bundled": 159971,
"minified": 57363,
"gzipped": 16547
},
"umd/react-router-dom.min.js": {
"bundled": 97822,
"minified": 34506,
"gzipped": 10211
"bundled": 97748,
"minified": 34398,
"gzipped": 10227
}
}
91 changes: 48 additions & 43 deletions packages/react-router-dom/modules/Link.js
Expand Up @@ -8,61 +8,66 @@ function isModifiedEvent(event) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}

/**
* The public API for rendering a history-aware <a>.
*/
class Link extends React.Component {
handleClick(event, history) {
if (this.props.onClick) this.props.onClick(event);

if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
(!this.props.target || this.props.target === "_self") && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
function LinkAnchor({ innerRef, navigate, onClick, ...rest }) {
const { target } = rest;

const method = this.props.replace ? history.replace : history.push;
return (
<a
{...rest}
ref={innerRef} // TODO: Use forwardRef instead
onClick={event => {
if (onClick) onClick(event);

method(this.props.to);
}
}
if (
!event.defaultPrevented && // onClick prevented default
event.button === 0 && // ignore everything but left clicks
(!target || target === "_self") && // let browser handle "target=_blank" etc.
!isModifiedEvent(event) // ignore clicks with modifier keys
) {
event.preventDefault();
navigate();
}
}}
/>
);
}

render() {
const { innerRef, replace, to, ...rest } = this.props; // eslint-disable-line no-unused-vars
/**
* The public API for rendering a history-aware <a>.
*/
function Link({ component = LinkAnchor, replace, to, ...rest }) {
return (
<RouterContext.Consumer>
{context => {
invariant(context, "You should not use <Link> outside a <Router>");

return (
<RouterContext.Consumer>
{context => {
invariant(context, "You should not use <Link> outside a <Router>");
const { history } = context;

const location =
typeof to === "string"
? createLocation(to, null, null, context.location)
: to;
const href = location ? context.history.createHref(location) : "";
const location =
typeof to === "string"
? createLocation(to, null, null, context.location)
: to;
const href = location ? history.createHref(location) : "";

return (
<a
{...rest}
onClick={event => this.handleClick(event, context.history)}
href={href}
ref={innerRef}
/>
);
}}
</RouterContext.Consumer>
);
}
return React.createElement(component, {
...rest,
href,
navigate() {
const method = replace ? history.replace : history.push;
method(to);
}
});
}}
</RouterContext.Consumer>
);
}

if (__DEV__) {
const toType = PropTypes.oneOfType([PropTypes.string, PropTypes.object]);
const innerRefType = PropTypes.oneOfType([PropTypes.string, PropTypes.func]);
const refType = PropTypes.oneOfType([PropTypes.string, PropTypes.func]);

Link.propTypes = {
innerRef: innerRefType,
innerRef: refType,
onClick: PropTypes.func,
replace: PropTypes.bool,
target: PropTypes.string,
Expand Down
32 changes: 27 additions & 5 deletions packages/react-router-dom/modules/__tests__/Link-test.js
Expand Up @@ -91,12 +91,10 @@ describe("A <Link>", () => {
});
});

it("exposes its ref via an innerRef prop", done => {
it("exposes its ref via an innerRef prop", () => {
let refNode;
function refCallback(n) {
if (n) {
expect(n.tagName).toEqual("A");
done();
}
refNode = n;
}

renderStrict(
Expand All @@ -107,6 +105,30 @@ describe("A <Link>", () => {
</MemoryRouter>,
node
);

expect(refNode).not.toBe(undefined);
expect(refNode.tagName).toEqual("A");
});

it("uses a custom component prop", () => {
let linkProps;
function MyComponent(p) {
linkProps = p;
return null;
}

renderStrict(
<MemoryRouter>
<Link component={MyComponent} to="/">
link
</Link>
</MemoryRouter>,
node
);

expect(linkProps).not.toBe(undefined);
expect(typeof linkProps.href).toBe("string");
expect(typeof linkProps.navigate).toBe("function");
});

describe("with a <HashRouter>", () => {
Expand Down

0 comments on commit 3ed5482

Please sign in to comment.