Link.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. import React from "react";
  2. import { __RouterContext as RouterContext } from "react-router";
  3. import { createPath } from 'history';
  4. import PropTypes from "prop-types";
  5. import invariant from "tiny-invariant";
  6. import {
  7. resolveToLocation,
  8. normalizeToLocation
  9. } from "./utils/locationUtils.js";
  10. // React 15 compat
  11. const forwardRefShim = C => C;
  12. let { forwardRef } = React;
  13. if (typeof forwardRef === "undefined") {
  14. forwardRef = forwardRefShim;
  15. }
  16. function isModifiedEvent(event) {
  17. return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
  18. }
  19. const LinkAnchor = forwardRef(
  20. (
  21. {
  22. innerRef, // TODO: deprecate
  23. navigate,
  24. onClick,
  25. ...rest
  26. },
  27. forwardedRef
  28. ) => {
  29. const { target } = rest;
  30. let props = {
  31. ...rest,
  32. onClick: event => {
  33. try {
  34. if (onClick) onClick(event);
  35. } catch (ex) {
  36. event.preventDefault();
  37. throw ex;
  38. }
  39. if (
  40. !event.defaultPrevented && // onClick prevented default
  41. event.button === 0 && // ignore everything but left clicks
  42. (!target || target === "_self") && // let browser handle "target=_blank" etc.
  43. !isModifiedEvent(event) // ignore clicks with modifier keys
  44. ) {
  45. event.preventDefault();
  46. navigate();
  47. }
  48. }
  49. };
  50. // React 15 compat
  51. if (forwardRefShim !== forwardRef) {
  52. props.ref = forwardedRef || innerRef;
  53. } else {
  54. props.ref = innerRef;
  55. }
  56. /* eslint-disable-next-line jsx-a11y/anchor-has-content */
  57. return <a {...props} />;
  58. }
  59. );
  60. if (__DEV__) {
  61. LinkAnchor.displayName = "LinkAnchor";
  62. }
  63. /**
  64. * The public API for rendering a history-aware <a>.
  65. */
  66. const Link = forwardRef(
  67. (
  68. {
  69. component = LinkAnchor,
  70. replace,
  71. to,
  72. innerRef, // TODO: deprecate
  73. ...rest
  74. },
  75. forwardedRef
  76. ) => {
  77. return (
  78. <RouterContext.Consumer>
  79. {context => {
  80. invariant(context, "You should not use <Link> outside a <Router>");
  81. const { history } = context;
  82. const location = normalizeToLocation(
  83. resolveToLocation(to, context.location),
  84. context.location
  85. );
  86. const href = location ? history.createHref(location) : "";
  87. const props = {
  88. ...rest,
  89. href,
  90. navigate() {
  91. const location = resolveToLocation(to, context.location);
  92. const isDuplicateNavigation = createPath(context.location) === createPath(normalizeToLocation(location));
  93. const method = (replace || isDuplicateNavigation) ? history.replace : history.push;
  94. method(location);
  95. }
  96. };
  97. // React 15 compat
  98. if (forwardRefShim !== forwardRef) {
  99. props.ref = forwardedRef || innerRef;
  100. } else {
  101. props.innerRef = innerRef;
  102. }
  103. return React.createElement(component, props);
  104. }}
  105. </RouterContext.Consumer>
  106. );
  107. }
  108. );
  109. if (__DEV__) {
  110. const toType = PropTypes.oneOfType([
  111. PropTypes.string,
  112. PropTypes.object,
  113. PropTypes.func
  114. ]);
  115. const refType = PropTypes.oneOfType([
  116. PropTypes.string,
  117. PropTypes.func,
  118. PropTypes.shape({ current: PropTypes.any })
  119. ]);
  120. Link.displayName = "Link";
  121. Link.propTypes = {
  122. innerRef: refType,
  123. onClick: PropTypes.func,
  124. replace: PropTypes.bool,
  125. target: PropTypes.string,
  126. to: toType.isRequired
  127. };
  128. }
  129. export default Link;