Link.js 3.3 KB

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