Skip to content

Commit 9665659

Browse files
committed
Fix element <Route children>
This commit fixes <Route children> so that they do not render if the route does not match and they are elements. <Route children> functions still work as they did previously. Fixes #6362
1 parent 0394c5e commit 9665659

File tree

2 files changed

+148
-56
lines changed

2 files changed

+148
-56
lines changed

packages/react-router/modules/Route.js

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,19 @@ function isEmptyChildren(children) {
1111
return React.Children.count(children) === 0;
1212
}
1313

14+
function 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+
1427
/**
1528
* The public API for matching a single path and rendering.
1629
*/
@@ -25,8 +38,8 @@ class Route extends React.Component {
2538
const match = this.props.computedMatch
2639
? this.props.computedMatch // <Switch> already computed the match for us
2740
: this.props.path
28-
? matchPath(location.pathname, this.props)
29-
: context.match;
41+
? matchPath(location.pathname, this.props)
42+
: context.match;
3043

3144
const props = { ...context, location, match };
3245

@@ -38,36 +51,25 @@ class Route extends React.Component {
3851
children = null;
3952
}
4053

41-
if (typeof children === "function") {
42-
children = children(props);
43-
44-
if (children === undefined) {
45-
if (__DEV__) {
46-
const { path } = this.props;
47-
48-
warning(
49-
false,
50-
"You returned `undefined` from the `children` function of " +
51-
`<Route${path ? ` path="${path}"` : ""}>, but you ` +
52-
"should have returned a React element or `null`"
53-
);
54-
}
55-
56-
children = null;
57-
}
58-
}
59-
6054
return (
6155
<RouterContext.Provider value={props}>
62-
{children && !isEmptyChildren(children)
56+
{props.match
6357
? children
64-
: props.match
65-
? component
66-
? React.createElement(component, props)
67-
: render
68-
? render(props)
69-
: null
70-
: null}
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}
7173
</RouterContext.Provider>
7274
);
7375
}}

packages/react-router/modules/__tests__/Route-test.js

Lines changed: 117 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,43 +22,133 @@ describe("A <Route>", () => {
2222
});
2323
});
2424

25-
it("renders when it matches", () => {
26-
const text = "cupcakes";
25+
describe("with a child element", () => {
26+
it("renders when it matches", () => {
27+
const text = "cupcakes";
2728

28-
renderStrict(
29-
<MemoryRouter initialEntries={["/cupcakes"]}>
30-
<Route path="/cupcakes" render={() => <h1>{text}</h1>} />
31-
</MemoryRouter>,
32-
node
33-
);
29+
renderStrict(
30+
<MemoryRouter initialEntries={["/cupcakes"]}>
31+
<Route path="/cupcakes">
32+
<h1>{text}</h1>
33+
</Route>
34+
</MemoryRouter>,
35+
node
36+
);
37+
38+
expect(node.innerHTML).toContain(text);
39+
});
40+
41+
it("renders when it matches at the root URL", () => {
42+
const text = "cupcakes";
43+
44+
renderStrict(
45+
<MemoryRouter initialEntries={["/"]}>
46+
<Route path="/">
47+
<h1>{text}</h1>
48+
</Route>
49+
</MemoryRouter>,
50+
node
51+
);
52+
53+
expect(node.innerHTML).toContain(text);
54+
});
55+
56+
it("does not render when it does not match", () => {
57+
const text = "bubblegum";
3458

35-
expect(node.innerHTML).toContain(text);
59+
renderStrict(
60+
<MemoryRouter initialEntries={["/bunnies"]}>
61+
<Route path="/flowers">
62+
<h1>{text}</h1>
63+
</Route>
64+
</MemoryRouter>,
65+
node
66+
);
67+
68+
expect(node.innerHTML).not.toContain(text);
69+
});
3670
});
3771

38-
it("renders when it matches at the root URL", () => {
39-
const text = "cupcakes";
72+
describe("with a children function", () => {
73+
it("renders when it matches", () => {
74+
const text = "cupcakes";
4075

41-
renderStrict(
42-
<MemoryRouter initialEntries={["/"]}>
43-
<Route path="/" render={() => <h1>{text}</h1>} />
44-
</MemoryRouter>,
45-
node
46-
);
76+
renderStrict(
77+
<MemoryRouter initialEntries={["/cupcakes"]}>
78+
<Route path="/cupcakes" children={() => <h1>{text}</h1>} />
79+
</MemoryRouter>,
80+
node
81+
);
82+
83+
expect(node.innerHTML).toContain(text);
84+
});
4785

48-
expect(node.innerHTML).toContain(text);
86+
it("renders when it matches at the root URL", () => {
87+
const text = "cupcakes";
88+
89+
renderStrict(
90+
<MemoryRouter initialEntries={["/"]}>
91+
<Route path="/" children={() => <h1>{text}</h1>} />
92+
</MemoryRouter>,
93+
node
94+
);
95+
96+
expect(node.innerHTML).toContain(text);
97+
});
98+
99+
it("renders when it does not match", () => {
100+
const text = "bubblegum";
101+
102+
renderStrict(
103+
<MemoryRouter initialEntries={["/bunnies"]}>
104+
<Route path="/flowers" children={() => <h1>{text}</h1>} />
105+
</MemoryRouter>,
106+
node
107+
);
108+
109+
expect(node.innerHTML).toContain(text);
110+
});
49111
});
50112

51-
it("does not render when it does not match", () => {
52-
const text = "bubblegum";
113+
describe("with a render prop", () => {
114+
it("renders when it matches", () => {
115+
const text = "cupcakes";
53116

54-
renderStrict(
55-
<MemoryRouter initialEntries={["/bunnies"]}>
56-
<Route path="/flowers" render={() => <h1>{text}</h1>} />
57-
</MemoryRouter>,
58-
node
59-
);
117+
renderStrict(
118+
<MemoryRouter initialEntries={["/cupcakes"]}>
119+
<Route path="/cupcakes" render={() => <h1>{text}</h1>} />
120+
</MemoryRouter>,
121+
node
122+
);
60123

61-
expect(node.innerHTML).not.toContain(text);
124+
expect(node.innerHTML).toContain(text);
125+
});
126+
127+
it("renders when it matches at the root URL", () => {
128+
const text = "cupcakes";
129+
130+
renderStrict(
131+
<MemoryRouter initialEntries={["/"]}>
132+
<Route path="/" render={() => <h1>{text}</h1>} />
133+
</MemoryRouter>,
134+
node
135+
);
136+
137+
expect(node.innerHTML).toContain(text);
138+
});
139+
140+
it("does not render when it does not match", () => {
141+
const text = "bubblegum";
142+
143+
renderStrict(
144+
<MemoryRouter initialEntries={["/bunnies"]}>
145+
<Route path="/flowers" render={() => <h1>{text}</h1>} />
146+
</MemoryRouter>,
147+
node
148+
);
149+
150+
expect(node.innerHTML).not.toContain(text);
151+
});
62152
});
63153

64154
it("matches using nextContext when updating", () => {

0 commit comments

Comments
 (0)