UNPKG

23.5 kBJavaScriptView Raw
1import createContext from 'mini-create-react-context';
2import _inheritsLoose from '@babel/runtime/helpers/esm/inheritsLoose';
3import React from 'react';
4import PropTypes from 'prop-types';
5import warning from 'tiny-warning';
6import { createMemoryHistory, createLocation, locationsAreEqual, createPath } from 'history';
7import invariant from 'tiny-invariant';
8import pathToRegexp from 'path-to-regexp';
9import _extends from '@babel/runtime/helpers/esm/extends';
10import { isValidElementType } from 'react-is';
11import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/esm/objectWithoutPropertiesLoose';
12import hoistStatics from 'hoist-non-react-statics';
13
14// TODO: Replace with React.createContext once we can assume React 16+
15
16var createNamedContext = function createNamedContext(name) {
17 var context = createContext();
18 context.displayName = name;
19 return context;
20};
21
22var context =
23/*#__PURE__*/
24createNamedContext("Router");
25
26/**
27 * The public API for putting history on context.
28 */
29
30var Router =
31/*#__PURE__*/
32function (_React$Component) {
33 _inheritsLoose(Router, _React$Component);
34
35 Router.computeRootMatch = function computeRootMatch(pathname) {
36 return {
37 path: "/",
38 url: "/",
39 params: {},
40 isExact: pathname === "/"
41 };
42 };
43
44 function Router(props) {
45 var _this;
46
47 _this = _React$Component.call(this, props) || this;
48 _this.state = {
49 location: props.history.location
50 }; // This is a bit of a hack. We have to start listening for location
51 // changes here in the constructor in case there are any <Redirect>s
52 // on the initial render. If there are, they will replace/push when
53 // they mount and since cDM fires in children before parents, we may
54 // get a new location before the <Router> is mounted.
55
56 _this._isMounted = false;
57 _this._pendingLocation = null;
58
59 if (!props.staticContext) {
60 _this.unlisten = props.history.listen(function (location) {
61 if (_this._isMounted) {
62 _this.setState({
63 location: location
64 });
65 } else {
66 _this._pendingLocation = location;
67 }
68 });
69 }
70
71 return _this;
72 }
73
74 var _proto = Router.prototype;
75
76 _proto.componentDidMount = function componentDidMount() {
77 this._isMounted = true;
78
79 if (this._pendingLocation) {
80 this.setState({
81 location: this._pendingLocation
82 });
83 }
84 };
85
86 _proto.componentWillUnmount = function componentWillUnmount() {
87 if (this.unlisten) this.unlisten();
88 };
89
90 _proto.render = function render() {
91 return React.createElement(context.Provider, {
92 children: this.props.children || null,
93 value: {
94 history: this.props.history,
95 location: this.state.location,
96 match: Router.computeRootMatch(this.state.location.pathname),
97 staticContext: this.props.staticContext
98 }
99 });
100 };
101
102 return Router;
103}(React.Component);
104
105if (process.env.NODE_ENV !== "production") {
106 Router.propTypes = {
107 children: PropTypes.node,
108 history: PropTypes.object.isRequired,
109 staticContext: PropTypes.object
110 };
111
112 Router.prototype.componentDidUpdate = function (prevProps) {
113 process.env.NODE_ENV !== "production" ? warning(prevProps.history === this.props.history, "You cannot change <Router history>") : void 0;
114 };
115}
116
117/**
118 * The public API for a <Router> that stores location in memory.
119 */
120
121var MemoryRouter =
122/*#__PURE__*/
123function (_React$Component) {
124 _inheritsLoose(MemoryRouter, _React$Component);
125
126 function MemoryRouter() {
127 var _this;
128
129 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
130 args[_key] = arguments[_key];
131 }
132
133 _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this;
134 _this.history = createMemoryHistory(_this.props);
135 return _this;
136 }
137
138 var _proto = MemoryRouter.prototype;
139
140 _proto.render = function render() {
141 return React.createElement(Router, {
142 history: this.history,
143 children: this.props.children
144 });
145 };
146
147 return MemoryRouter;
148}(React.Component);
149
150if (process.env.NODE_ENV !== "production") {
151 MemoryRouter.propTypes = {
152 initialEntries: PropTypes.array,
153 initialIndex: PropTypes.number,
154 getUserConfirmation: PropTypes.func,
155 keyLength: PropTypes.number,
156 children: PropTypes.node
157 };
158
159 MemoryRouter.prototype.componentDidMount = function () {
160 process.env.NODE_ENV !== "production" ? warning(!this.props.history, "<MemoryRouter> ignores the history prop. To use a custom history, " + "use `import { Router }` instead of `import { MemoryRouter as Router }`.") : void 0;
161 };
162}
163
164var Lifecycle =
165/*#__PURE__*/
166function (_React$Component) {
167 _inheritsLoose(Lifecycle, _React$Component);
168
169 function Lifecycle() {
170 return _React$Component.apply(this, arguments) || this;
171 }
172
173 var _proto = Lifecycle.prototype;
174
175 _proto.componentDidMount = function componentDidMount() {
176 if (this.props.onMount) this.props.onMount.call(this, this);
177 };
178
179 _proto.componentDidUpdate = function componentDidUpdate(prevProps) {
180 if (this.props.onUpdate) this.props.onUpdate.call(this, this, prevProps);
181 };
182
183 _proto.componentWillUnmount = function componentWillUnmount() {
184 if (this.props.onUnmount) this.props.onUnmount.call(this, this);
185 };
186
187 _proto.render = function render() {
188 return null;
189 };
190
191 return Lifecycle;
192}(React.Component);
193
194/**
195 * The public API for prompting the user before navigating away from a screen.
196 */
197
198function Prompt(_ref) {
199 var message = _ref.message,
200 _ref$when = _ref.when,
201 when = _ref$when === void 0 ? true : _ref$when;
202 return React.createElement(context.Consumer, null, function (context$$1) {
203 !context$$1 ? process.env.NODE_ENV !== "production" ? invariant(false, "You should not use <Prompt> outside a <Router>") : invariant(false) : void 0;
204 if (!when || context$$1.staticContext) return null;
205 var method = context$$1.history.block;
206 return React.createElement(Lifecycle, {
207 onMount: function onMount(self) {
208 self.release = method(message);
209 },
210 onUpdate: function onUpdate(self, prevProps) {
211 if (prevProps.message !== message) {
212 self.release();
213 self.release = method(message);
214 }
215 },
216 onUnmount: function onUnmount(self) {
217 self.release();
218 },
219 message: message
220 });
221 });
222}
223
224if (process.env.NODE_ENV !== "production") {
225 var messageType = PropTypes.oneOfType([PropTypes.func, PropTypes.string]);
226 Prompt.propTypes = {
227 when: PropTypes.bool,
228 message: messageType.isRequired
229 };
230}
231
232var cache = {};
233var cacheLimit = 10000;
234var cacheCount = 0;
235
236function compilePath(path) {
237 if (cache[path]) return cache[path];
238 var generator = pathToRegexp.compile(path);
239
240 if (cacheCount < cacheLimit) {
241 cache[path] = generator;
242 cacheCount++;
243 }
244
245 return generator;
246}
247/**
248 * Public API for generating a URL pathname from a path and parameters.
249 */
250
251
252function generatePath(path, params) {
253 if (path === void 0) {
254 path = "/";
255 }
256
257 if (params === void 0) {
258 params = {};
259 }
260
261 return path === "/" ? path : compilePath(path)(params, {
262 pretty: true
263 });
264}
265
266/**
267 * The public API for navigating programmatically with a component.
268 */
269
270function Redirect(_ref) {
271 var computedMatch = _ref.computedMatch,
272 to = _ref.to,
273 _ref$push = _ref.push,
274 push = _ref$push === void 0 ? false : _ref$push;
275 return React.createElement(context.Consumer, null, function (context$$1) {
276 !context$$1 ? process.env.NODE_ENV !== "production" ? invariant(false, "You should not use <Redirect> outside a <Router>") : invariant(false) : void 0;
277 var history = context$$1.history,
278 staticContext = context$$1.staticContext;
279 var method = push ? history.push : history.replace;
280 var location = createLocation(computedMatch ? typeof to === "string" ? generatePath(to, computedMatch.params) : _extends({}, to, {
281 pathname: generatePath(to.pathname, computedMatch.params)
282 }) : to); // When rendering in a static context,
283 // set the new location immediately.
284
285 if (staticContext) {
286 method(location);
287 return null;
288 }
289
290 return React.createElement(Lifecycle, {
291 onMount: function onMount() {
292 method(location);
293 },
294 onUpdate: function onUpdate(self, prevProps) {
295 var prevLocation = createLocation(prevProps.to);
296
297 if (!locationsAreEqual(prevLocation, _extends({}, location, {
298 key: prevLocation.key
299 }))) {
300 method(location);
301 }
302 },
303 to: to
304 });
305 });
306}
307
308if (process.env.NODE_ENV !== "production") {
309 Redirect.propTypes = {
310 push: PropTypes.bool,
311 from: PropTypes.string,
312 to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired
313 };
314}
315
316var cache$1 = {};
317var cacheLimit$1 = 10000;
318var cacheCount$1 = 0;
319
320function compilePath$1(path, options) {
321 var cacheKey = "" + options.end + options.strict + options.sensitive;
322 var pathCache = cache$1[cacheKey] || (cache$1[cacheKey] = {});
323 if (pathCache[path]) return pathCache[path];
324 var keys = [];
325 var regexp = pathToRegexp(path, keys, options);
326 var result = {
327 regexp: regexp,
328 keys: keys
329 };
330
331 if (cacheCount$1 < cacheLimit$1) {
332 pathCache[path] = result;
333 cacheCount$1++;
334 }
335
336 return result;
337}
338/**
339 * Public API for matching a URL pathname to a path.
340 */
341
342
343function matchPath(pathname, options) {
344 if (options === void 0) {
345 options = {};
346 }
347
348 if (typeof options === "string") options = {
349 path: options
350 };
351 var _options = options,
352 path = _options.path,
353 _options$exact = _options.exact,
354 exact = _options$exact === void 0 ? false : _options$exact,
355 _options$strict = _options.strict,
356 strict = _options$strict === void 0 ? false : _options$strict,
357 _options$sensitive = _options.sensitive,
358 sensitive = _options$sensitive === void 0 ? false : _options$sensitive;
359 var paths = [].concat(path);
360 return paths.reduce(function (matched, path) {
361 if (!path) return null;
362 if (matched) return matched;
363
364 var _compilePath = compilePath$1(path, {
365 end: exact,
366 strict: strict,
367 sensitive: sensitive
368 }),
369 regexp = _compilePath.regexp,
370 keys = _compilePath.keys;
371
372 var match = regexp.exec(pathname);
373 if (!match) return null;
374 var url = match[0],
375 values = match.slice(1);
376 var isExact = pathname === url;
377 if (exact && !isExact) return null;
378 return {
379 path: path,
380 // the path used to match
381 url: path === "/" && url === "" ? "/" : url,
382 // the matched portion of the URL
383 isExact: isExact,
384 // whether or not we matched exactly
385 params: keys.reduce(function (memo, key, index) {
386 memo[key.name] = values[index];
387 return memo;
388 }, {})
389 };
390 }, null);
391}
392
393function isEmptyChildren(children) {
394 return React.Children.count(children) === 0;
395}
396/**
397 * The public API for matching a single path and rendering.
398 */
399
400
401var Route =
402/*#__PURE__*/
403function (_React$Component) {
404 _inheritsLoose(Route, _React$Component);
405
406 function Route() {
407 return _React$Component.apply(this, arguments) || this;
408 }
409
410 var _proto = Route.prototype;
411
412 _proto.render = function render() {
413 var _this = this;
414
415 return React.createElement(context.Consumer, null, function (context$$1) {
416 !context$$1 ? process.env.NODE_ENV !== "production" ? invariant(false, "You should not use <Route> outside a <Router>") : invariant(false) : void 0;
417 var location = _this.props.location || context$$1.location;
418 var match = _this.props.computedMatch ? _this.props.computedMatch // <Switch> already computed the match for us
419 : _this.props.path ? matchPath(location.pathname, _this.props) : context$$1.match;
420
421 var props = _extends({}, context$$1, {
422 location: location,
423 match: match
424 });
425
426 var _this$props = _this.props,
427 children = _this$props.children,
428 component = _this$props.component,
429 render = _this$props.render; // Preact uses an empty array as children by
430 // default, so use null if that's the case.
431
432 if (Array.isArray(children) && children.length === 0) {
433 children = null;
434 }
435
436 if (typeof children === "function") {
437 children = children(props);
438
439 if (children === undefined) {
440 if (process.env.NODE_ENV !== "production") {
441 var path = _this.props.path;
442 process.env.NODE_ENV !== "production" ? warning(false, "You returned `undefined` from the `children` function of " + ("<Route" + (path ? " path=\"" + path + "\"" : "") + ">, but you ") + "should have returned a React element or `null`") : void 0;
443 }
444
445 children = null;
446 }
447 }
448
449 return React.createElement(context.Provider, {
450 value: props
451 }, children && !isEmptyChildren(children) ? children : props.match ? component ? React.createElement(component, props) : render ? render(props) : null : null);
452 });
453 };
454
455 return Route;
456}(React.Component);
457
458if (process.env.NODE_ENV !== "production") {
459 Route.propTypes = {
460 children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
461 component: function component(props, propName) {
462 if (props[propName] && !isValidElementType(props[propName])) {
463 return new Error("Invalid prop 'component' supplied to 'Route': the prop is not a valid React component");
464 }
465 },
466 exact: PropTypes.bool,
467 location: PropTypes.object,
468 path: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
469 render: PropTypes.func,
470 sensitive: PropTypes.bool,
471 strict: PropTypes.bool
472 };
473
474 Route.prototype.componentDidMount = function () {
475 process.env.NODE_ENV !== "production" ? warning(!(this.props.children && !isEmptyChildren(this.props.children) && this.props.component), "You should not use <Route component> and <Route children> in the same route; <Route component> will be ignored") : void 0;
476 process.env.NODE_ENV !== "production" ? warning(!(this.props.children && !isEmptyChildren(this.props.children) && this.props.render), "You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored") : void 0;
477 process.env.NODE_ENV !== "production" ? warning(!(this.props.component && this.props.render), "You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored") : void 0;
478 };
479
480 Route.prototype.componentDidUpdate = function (prevProps) {
481 process.env.NODE_ENV !== "production" ? warning(!(this.props.location && !prevProps.location), '<Route> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.') : void 0;
482 process.env.NODE_ENV !== "production" ? warning(!(!this.props.location && prevProps.location), '<Route> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.') : void 0;
483 };
484}
485
486function addLeadingSlash(path) {
487 return path.charAt(0) === "/" ? path : "/" + path;
488}
489
490function addBasename(basename, location) {
491 if (!basename) return location;
492 return _extends({}, location, {
493 pathname: addLeadingSlash(basename) + location.pathname
494 });
495}
496
497function stripBasename(basename, location) {
498 if (!basename) return location;
499 var base = addLeadingSlash(basename);
500 if (location.pathname.indexOf(base) !== 0) return location;
501 return _extends({}, location, {
502 pathname: location.pathname.substr(base.length)
503 });
504}
505
506function createURL(location) {
507 return typeof location === "string" ? location : createPath(location);
508}
509
510function staticHandler(methodName) {
511 return function () {
512 process.env.NODE_ENV !== "production" ? invariant(false, "You cannot %s with <StaticRouter>", methodName) : invariant(false);
513 };
514}
515
516function noop() {}
517/**
518 * The public top-level API for a "static" <Router>, so-called because it
519 * can't actually change the current location. Instead, it just records
520 * location changes in a context object. Useful mainly in testing and
521 * server-rendering scenarios.
522 */
523
524
525var StaticRouter =
526/*#__PURE__*/
527function (_React$Component) {
528 _inheritsLoose(StaticRouter, _React$Component);
529
530 function StaticRouter() {
531 var _this;
532
533 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
534 args[_key] = arguments[_key];
535 }
536
537 _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this;
538
539 _this.handlePush = function (location) {
540 return _this.navigateTo(location, "PUSH");
541 };
542
543 _this.handleReplace = function (location) {
544 return _this.navigateTo(location, "REPLACE");
545 };
546
547 _this.handleListen = function () {
548 return noop;
549 };
550
551 _this.handleBlock = function () {
552 return noop;
553 };
554
555 return _this;
556 }
557
558 var _proto = StaticRouter.prototype;
559
560 _proto.navigateTo = function navigateTo(location, action) {
561 var _this$props = this.props,
562 _this$props$basename = _this$props.basename,
563 basename = _this$props$basename === void 0 ? "" : _this$props$basename,
564 _this$props$context = _this$props.context,
565 context = _this$props$context === void 0 ? {} : _this$props$context;
566 context.action = action;
567 context.location = addBasename(basename, createLocation(location));
568 context.url = createURL(context.location);
569 };
570
571 _proto.render = function render() {
572 var _this$props2 = this.props,
573 _this$props2$basename = _this$props2.basename,
574 basename = _this$props2$basename === void 0 ? "" : _this$props2$basename,
575 _this$props2$context = _this$props2.context,
576 context = _this$props2$context === void 0 ? {} : _this$props2$context,
577 _this$props2$location = _this$props2.location,
578 location = _this$props2$location === void 0 ? "/" : _this$props2$location,
579 rest = _objectWithoutPropertiesLoose(_this$props2, ["basename", "context", "location"]);
580
581 var history = {
582 createHref: function createHref(path) {
583 return addLeadingSlash(basename + createURL(path));
584 },
585 action: "POP",
586 location: stripBasename(basename, createLocation(location)),
587 push: this.handlePush,
588 replace: this.handleReplace,
589 go: staticHandler("go"),
590 goBack: staticHandler("goBack"),
591 goForward: staticHandler("goForward"),
592 listen: this.handleListen,
593 block: this.handleBlock
594 };
595 return React.createElement(Router, _extends({}, rest, {
596 history: history,
597 staticContext: context
598 }));
599 };
600
601 return StaticRouter;
602}(React.Component);
603
604if (process.env.NODE_ENV !== "production") {
605 StaticRouter.propTypes = {
606 basename: PropTypes.string,
607 context: PropTypes.object,
608 location: PropTypes.oneOfType([PropTypes.string, PropTypes.object])
609 };
610
611 StaticRouter.prototype.componentDidMount = function () {
612 process.env.NODE_ENV !== "production" ? warning(!this.props.history, "<StaticRouter> ignores the history prop. To use a custom history, " + "use `import { Router }` instead of `import { StaticRouter as Router }`.") : void 0;
613 };
614}
615
616/**
617 * The public API for rendering the first <Route> that matches.
618 */
619
620var Switch =
621/*#__PURE__*/
622function (_React$Component) {
623 _inheritsLoose(Switch, _React$Component);
624
625 function Switch() {
626 return _React$Component.apply(this, arguments) || this;
627 }
628
629 var _proto = Switch.prototype;
630
631 _proto.render = function render() {
632 var _this = this;
633
634 return React.createElement(context.Consumer, null, function (context$$1) {
635 !context$$1 ? process.env.NODE_ENV !== "production" ? invariant(false, "You should not use <Switch> outside a <Router>") : invariant(false) : void 0;
636 var location = _this.props.location || context$$1.location;
637 var element, match; // We use React.Children.forEach instead of React.Children.toArray().find()
638 // here because toArray adds keys to all child elements and we do not want
639 // to trigger an unmount/remount for two <Route>s that render the same
640 // component at different URLs.
641
642 React.Children.forEach(_this.props.children, function (child) {
643 if (match == null && React.isValidElement(child)) {
644 element = child;
645 var path = child.props.path || child.props.from;
646 match = path ? matchPath(location.pathname, _extends({}, child.props, {
647 path: path
648 })) : context$$1.match;
649 }
650 });
651 return match ? React.cloneElement(element, {
652 location: location,
653 computedMatch: match
654 }) : null;
655 });
656 };
657
658 return Switch;
659}(React.Component);
660
661if (process.env.NODE_ENV !== "production") {
662 Switch.propTypes = {
663 children: PropTypes.node,
664 location: PropTypes.object
665 };
666
667 Switch.prototype.componentDidUpdate = function (prevProps) {
668 process.env.NODE_ENV !== "production" ? warning(!(this.props.location && !prevProps.location), '<Switch> elements should not change from uncontrolled to controlled (or vice versa). You initially used no "location" prop and then provided one on a subsequent render.') : void 0;
669 process.env.NODE_ENV !== "production" ? warning(!(!this.props.location && prevProps.location), '<Switch> elements should not change from controlled to uncontrolled (or vice versa). You provided a "location" prop initially but omitted it on a subsequent render.') : void 0;
670 };
671}
672
673/**
674 * A public higher-order component to access the imperative API
675 */
676
677function withRouter(Component) {
678 var displayName = "withRouter(" + (Component.displayName || Component.name) + ")";
679
680 var C = function C(props) {
681 var wrappedComponentRef = props.wrappedComponentRef,
682 remainingProps = _objectWithoutPropertiesLoose(props, ["wrappedComponentRef"]);
683
684 return React.createElement(context.Consumer, null, function (context$$1) {
685 !context$$1 ? process.env.NODE_ENV !== "production" ? invariant(false, "You should not use <" + displayName + " /> outside a <Router>") : invariant(false) : void 0;
686 return React.createElement(Component, _extends({}, remainingProps, context$$1, {
687 ref: wrappedComponentRef
688 }));
689 });
690 };
691
692 C.displayName = displayName;
693 C.WrappedComponent = Component;
694
695 if (process.env.NODE_ENV !== "production") {
696 C.propTypes = {
697 wrappedComponentRef: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.object])
698 };
699 }
700
701 return hoistStatics(C, Component);
702}
703
704if (process.env.NODE_ENV !== "production") {
705 if (typeof window !== "undefined") {
706 var global = window;
707 var key = "__react_router_build__";
708 var buildNames = {
709 cjs: "CommonJS",
710 esm: "ES modules",
711 umd: "UMD"
712 };
713
714 if (global[key] && global[key] !== "esm") {
715 var initialBuildName = buildNames[global[key]];
716 var secondaryBuildName = buildNames["esm"]; // TODO: Add link to article that explains in detail how to avoid
717 // loading 2 different builds.
718
719 throw new Error("You are loading the " + secondaryBuildName + " build of React Router " + ("on a page that is already running the " + initialBuildName + " ") + "build, so things won't work right.");
720 }
721
722 global[key] = "esm";
723 }
724}
725
726export { MemoryRouter, Prompt, Redirect, Route, Router, StaticRouter, Switch, generatePath, matchPath, withRouter, context as __RouterContext };