UNPKG

4.43 kBJavaScriptView Raw
1import React from "react";
2import { isValidElementType } from "react-is";
3import PropTypes from "prop-types";
4import invariant from "tiny-invariant";
5import warning from "tiny-warning";
6
7import RouterContext from "./RouterContext.js";
8import matchPath from "./matchPath.js";
9
10function isEmptyChildren(children) {
11 return React.Children.count(children) === 0;
12}
13
14function evalChildrenDev(children, props, path) {
15 const value = children(props);
16
17 warning(
18 value !== undefined,
19 "You returned `undefined` from the `children` function of " +
20 `<Route${path ? ` path="${path}"` : ""}>, but you ` +
21 "should have returned a React element or `null`"
22 );
23
24 return value || null;
25}
26
27/**
28 * The public API for matching a single path and rendering.
29 */
30class Route extends React.Component {
31 render() {
32 return (
33 <RouterContext.Consumer>
34 {context => {
35 invariant(context, "You should not use <Route> outside a <Router>");
36
37 const location = this.props.location || context.location;
38 const match = this.props.computedMatch
39 ? this.props.computedMatch // <Switch> already computed the match for us
40 : this.props.path
41 ? matchPath(location.pathname, this.props)
42 : context.match;
43
44 const props = { ...context, location, match };
45
46 let { children, component, render } = this.props;
47
48 // Preact uses an empty array as children by
49 // default, so use null if that's the case.
50 if (Array.isArray(children) && children.length === 0) {
51 children = null;
52 }
53
54 return (
55 <RouterContext.Provider value={props}>
56 {props.match
57 ? children
58 ? typeof children === "function"
59 ? __DEV__
60 ? evalChildrenDev(children, props, this.props.path)
61 : children(props)
62 : children
63 : component
64 ? React.createElement(component, props)
65 : render
66 ? render(props)
67 : null
68 : typeof children === "function"
69 ? __DEV__
70 ? evalChildrenDev(children, props, this.props.path)
71 : children(props)
72 : null}
73 </RouterContext.Provider>
74 );
75 }}
76 </RouterContext.Consumer>
77 );
78 }
79}
80
81if (__DEV__) {
82 Route.propTypes = {
83 children: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
84 component: (props, propName) => {
85 if (props[propName] && !isValidElementType(props[propName])) {
86 return new Error(
87 `Invalid prop 'component' supplied to 'Route': the prop is not a valid React component`
88 );
89 }
90 },
91 exact: PropTypes.bool,
92 location: PropTypes.object,
93 path: PropTypes.oneOfType([
94 PropTypes.string,
95 PropTypes.arrayOf(PropTypes.string)
96 ]),
97 render: PropTypes.func,
98 sensitive: PropTypes.bool,
99 strict: PropTypes.bool
100 };
101
102 Route.prototype.componentDidMount = function() {
103 warning(
104 !(
105 this.props.children &&
106 !isEmptyChildren(this.props.children) &&
107 this.props.component
108 ),
109 "You should not use <Route component> and <Route children> in the same route; <Route component> will be ignored"
110 );
111
112 warning(
113 !(
114 this.props.children &&
115 !isEmptyChildren(this.props.children) &&
116 this.props.render
117 ),
118 "You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored"
119 );
120
121 warning(
122 !(this.props.component && this.props.render),
123 "You should not use <Route component> and <Route render> in the same route; <Route render> will be ignored"
124 );
125 };
126
127 Route.prototype.componentDidUpdate = function(prevProps) {
128 warning(
129 !(this.props.location && !prevProps.location),
130 '<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.'
131 );
132
133 warning(
134 !(!this.props.location && prevProps.location),
135 '<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.'
136 );
137 };
138}
139
140export default Route;