Skip to content

Commit

Permalink
Add <Link component> prop (#6462)
Browse files Browse the repository at this point in the history
See #5437
  • Loading branch information
mjackson authored and timdorr committed Jun 13, 2019
1 parent bb802cd commit 520a0ac
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 68 deletions.
24 changes: 12 additions & 12 deletions packages/react-router-dom/.size-snapshot.json
@@ -1,26 +1,26 @@
{
"esm/react-router-dom.js": {
"bundled": 8874,
"minified": 5312,
"gzipped": 1711,
"bundled": 8700,
"minified": 5180,
"gzipped": 1664,
"treeshaked": {
"rollup": {
"code": 508,
"import_statements": 417
"code": 379,
"import_statements": 355
},
"webpack": {
"code": 1800
"code": 1612
}
}
},
"umd/react-router-dom.js": {
"bundled": 159933,
"minified": 56923,
"gzipped": 16433
"bundled": 159744,
"minified": 56768,
"gzipped": 16411
},
"umd/react-router-dom.min.js": {
"bundled": 96671,
"minified": 33875,
"gzipped": 9980
"bundled": 96492,
"minified": 33716,
"gzipped": 9979
}
}
105 changes: 54 additions & 51 deletions packages/react-router-dom/modules/Link.js
Expand Up @@ -8,63 +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, context) {
try {
if (this.props.onClick) this.props.onClick(event);
} catch (ex) {
event.preventDefault();
throw ex;
}

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 location = resolveToLocation(this.props.to, context.location);
return (
<a
{...rest}
ref={innerRef} // TODO: Use forwardRef instead
onClick={event => {
try {
if (onClick) onClick(event);
} catch (ex) {
event.preventDefault();
throw ex;
}

const method = this.props.replace
? context.history.replace
: context.history.push;
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();
}
}}
/>
);
}

method(location);
}
}
/**
* 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>");

render() {
const { innerRef, replace, to, ...rest } = this.props; // eslint-disable-line no-unused-vars
const { history } = context;

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

const location = normalizeToLocation(
resolveToLocation(to, context.location),
context.location
);
const href = location ? history.createHref(location) : "";

const href = location ? context.history.createHref(location) : "";
return React.createElement(component, {
...rest,
href,
navigate() {
const location = resolveToLocation(to, context.location);
const method = replace ? history.replace : history.push;

return (
<a
{...rest}
onClick={event => this.handleClick(event, context)}
href={href}
ref={innerRef}
/>
);
}}
</RouterContext.Consumer>
);
}
method(location);
}
});
}}
</RouterContext.Consumer>
);
}

if (__DEV__) {
Expand All @@ -73,14 +76,14 @@ if (__DEV__) {
PropTypes.object,
PropTypes.func
]);
const innerRefType = PropTypes.oneOfType([
const refType = PropTypes.oneOfType([
PropTypes.string,
PropTypes.func,
PropTypes.shape({ current: PropTypes.any })
]);

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 @@ -120,12 +120,10 @@ describe("A <Link>", () => {
});
});

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

renderStrict(
Expand All @@ -136,6 +134,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");
});

it("exposes its ref via an innerRef RefObject prop", done => {
Expand Down

0 comments on commit 520a0ac

Please sign in to comment.