Skip to content

Commit a051c64

Browse files
committed
Add childProps to Locations and Pages. This allows passing props to all handlers.
Fixes #104
1 parent d314a3e commit a051c64

File tree

3 files changed

+84
-23
lines changed

3 files changed

+84
-23
lines changed

lib/RouteRenderingMixin.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,30 @@ var assign = Object.assign || require('object-assign');
99
*/
1010
var RouteRenderingMixin = {
1111

12+
// Props passed at the `childProps` key are passed to all handlers.
13+
getChildProps: function() {
14+
var childProps = this.props.childProps || {};
15+
// Merge up from parents, with inner props taking priority.
16+
var parent = this.getParentRouter();
17+
if (parent) {
18+
childProps = assign({}, parent.getChildProps(), childProps);
19+
}
20+
return childProps;
21+
},
22+
1223
renderRouteHandler: function(props) {
1324
if (!this.state.match.route) {
1425
throw new Error("React-router-component: No route matched! Did you define a NotFound route?");
1526
}
1627
var handler = this.state.handler;
1728
var matchProps = this.state.matchProps;
1829

19-
props = assign({ref: this.state.match.route.ref}, props, matchProps);
30+
props = assign({ref: this.state.match.route.ref}, this.getChildProps(), props, matchProps);
2031
// If we were passed an element, we need to clone it before passing it along.
2132
if (React.isValidElement(handler)) {
22-
return React.cloneElement(handler, props);
33+
// Be sure to keep the props that were already set on the handler.
34+
// Otherwise, a handler like <div className="foo">bar</div> would have its className lost.
35+
return React.cloneElement(handler, assign(props, handler.props));
2336
}
2437
return React.createElement(handler, props);
2538
}

lib/Router.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,10 @@ function createRouter(name, component) {
3535
var props = assign({}, this.props);
3636
delete props.component;
3737
delete props.children;
38+
delete props.childProps;
3839

3940
// Render the Route's handler.
40-
var handler = this.renderRouteHandler();
41+
var handler = this.renderRouteHandler(this.props.childProps);
4142
return React.createElement(this.props.component, props, handler);
4243
}
4344
});

tests/server/server.js

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,31 +42,31 @@ describe('react-router-component (on server)', function() {
4242
}
4343

4444
it('renders to /', function() {
45-
var markup = ReactDOMServer.renderToString(<App path="/" />);
45+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/" />);
4646
assert(markup.match(/class="App"/));
4747
assert(markup.match(/mainpage/));
4848
});
4949

5050
it('renders to /:slug', function() {
51-
var markup = ReactDOMServer.renderToString(<App path="/x/hello" />);
51+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/x/hello" />);
5252
assert(markup.match(/class="App"/));
5353
assert(markup.match(/hello/));
5454
});
5555

5656
it('renders with regex', function() {
57-
var markup = ReactDOMServer.renderToString(<App path="/y/ohhai" />);
57+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/y/ohhai" />);
5858
assert(markup.match(/class="App"/));
5959
assert(markup.match(/ohhai/));
6060
});
6161

6262
it('renders with regex and urlPatternOptions(matchKeys)', function() {
63-
var markup = ReactDOMServer.renderToString(<App path="/z/one/two" />);
63+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/z/one/two" />);
6464
assert(markup.match(/class="App"/));
6565
assert(markup.match(/onetwo/));
6666
});
6767

6868
it('renders to empty on notfound', function() {
69-
var markup = ReactDOMServer.renderToString(<App path="/notfound" />);
69+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/notfound" />);
7070
assert(markup.match(/class="App"/));
7171
assert(markup.match(/not_found/));
7272
});
@@ -82,8 +82,8 @@ describe('react-router-component (on server)', function() {
8282
}
8383

8484
it('renders to <body>', function() {
85-
var markup = ReactDOMServer.renderToString(<App path="/" />);
86-
assert(markup.match(/<body [^>]+><div [^>]+>mainpage<\/div><\/body>/));
85+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/" />);
86+
assert.equal(markup, '<body class="App"><div>mainpage</div></body>');
8787
});
8888
});
8989

@@ -114,21 +114,21 @@ describe('react-router-component (on server)', function() {
114114
}
115115

116116
it('renders Link component with href scoped to its prefix', function() {
117-
var markup = ReactDOMServer.renderToString(<App path="/x/nice/hello" />);
117+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/x/nice/hello" />);
118118
assert(markup.match(/class="App"/));
119119
assert(markup.match(/class="X"/));
120120
assert(markup.match(/href="\/x\/nice\/hi"/));
121121
});
122122

123123
it('renders global Link component with correct href (not scoped to a router)', function() {
124-
var markup = ReactDOMServer.renderToString(<App path="/x/nice/hello2" />);
124+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/x/nice/hello2" />);
125125
assert(markup.match(/class="App"/));
126126
assert(markup.match(/class="X"/));
127127
assert(markup.match(/href="\/hi"/));
128128
});
129129

130130
it('renders Link component with href scoped to its nested context prefix', function() {
131-
var markup = ReactDOMServer.renderToString(<App path="/x/nice/hello3/welcome" />);
131+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/x/nice/hello3/welcome" />);
132132
assert(markup.match(/class="App"/));
133133
assert(markup.match(/class="X"/));
134134
assert(markup.match(/class="Y"/));
@@ -171,23 +171,23 @@ describe('react-router-component (on server)', function() {
171171
}
172172

173173
it('renders Link component with href scoped to its prefix', function() {
174-
var markup = ReactDOMServer.renderToString(<App path="/l1/nice" />);
174+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/l1/nice" />);
175175
assert(markup.match(/class="App"/));
176176
assert(markup.match(/class="L1"/));
177177
assert(markup.match(/href="\/l1\/nice\/l2"/));
178178
assert(markup.match(/data-slug="nice"/));
179179
});
180180

181181
it('renders Link component with href scoped to its prefix - trailing slash', function() {
182-
var markup = ReactDOMServer.renderToString(<App path="/l1/nice/" />);
182+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/l1/nice/" />);
183183
assert(markup.match(/class="App"/));
184184
assert(markup.match(/class="L1"/));
185185
assert(markup.match(/href="\/l1\/nice\/l2"/));
186186
assert(markup.match(/data-slug="nice"/));
187187
});
188188

189189
it('renders nested Link component with href scoped to its prefix', function() {
190-
var markup = ReactDOMServer.renderToString(<App path="/l1/nice/l2" />);
190+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/l1/nice/l2" />);
191191
assert(markup.match(/class="App"/));
192192
assert(markup.match(/class="L1"/));
193193
assert(markup.match(/class="L2"/));
@@ -196,7 +196,7 @@ describe('react-router-component (on server)', function() {
196196
});
197197

198198
it('renders global Link component with correct href (not scoped to a router)', function() {
199-
var markup = ReactDOMServer.renderToString(<App path="/l1/nice/l2/foo" />);
199+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/l1/nice/l2/foo" />);
200200
assert(markup.match(/class="App"/));
201201
assert(markup.match(/class="L2"/));
202202
assert(markup.match(/href="\/hi"/));
@@ -253,7 +253,7 @@ describe('react-router-component (on server)', function() {
253253
};
254254

255255
it('renders to / with context intact', function() {
256-
var markup = ReactDOMServer.renderToString(<App path="/" />);
256+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/" />);
257257
assert(markup.match(/class="App"/));
258258
assert(markup.match(/flux_value/));
259259
});
@@ -302,26 +302,73 @@ describe('react-router-component (on server)', function() {
302302
}
303303

304304
it('passes urlPatternOptions from parent <Locations>', function() {
305-
var markup = ReactDOMServer.renderToString(<App path="/start/1/biff/baz" />);
305+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/start/1/biff/baz" />);
306306
assert(markup.match(/biff\|baz/));
307307
});
308308

309309
it('merges urlPatternOptions from parent <Locations> and a <Location>', function() {
310-
var markup = ReactDOMServer.renderToString(<App path="/start/2/BIFF/BA" />);
310+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/start/2/BIFF/BA" />);
311311
assert(markup.match(/BIFF\|BA/));
312-
markup = ReactDOMServer.renderToString(<App path="/start/2/biff/ba" />);
312+
markup = ReactDOMServer.renderToStaticMarkup(<App path="/start/2/biff/ba" />);
313313
assert(markup.match(/not found/));
314314
});
315315

316316
it('gives urlPatternOptions on route precedence over router', function() {
317-
var markup = ReactDOMServer.renderToString(<App path="/start/3/biff/boff" />);
317+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/start/3/biff/boff" />);
318318
assert(markup.match(/biff\|boff/));
319319
});
320320

321321
it('inherits from parent contextual router', function() {
322-
var markup = ReactDOMServer.renderToString(<App path="/start/4/foobar" />);
322+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/start/4/foobar" />);
323323
assert(markup.match(/undefined\|undefinedbar/));
324324
});
325325
});
326326

327+
describe('props on router (childProps)', function() {
328+
class App extends React.Component {
329+
render() {
330+
return (
331+
<Locations className="X" childProps={{className: "A", 'data-from-first': 'A'}} path={this.props.path}>
332+
<Location path="/" handler={<div>mainpage</div>} />
333+
<Location path="/hasclassname" handler={<div className="ownClassname" />} />
334+
<Location path="/nested/*" handler={
335+
<Locations className="Y" data-own-prop={true} contextual={true} childProps={{className: "B", 'data-from-second': 'B'}}>
336+
<Location path="/foo" handler={<div>foo</div>} />
337+
{/* Rabbit hole it */}
338+
<Location path="/nestedAgain/*" handler={
339+
<Locations className="Z" data-own-prop={true} contextual={true} childProps={{className: "C", 'data-from-third': 'C'}}>
340+
<Location path="/bar" handler={<div>bar</div>} />
341+
</Locations>}
342+
/>
343+
</Locations>} />
344+
</Locations>
345+
);
346+
}
347+
}
348+
349+
it('passes className', function() {
350+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/" />);
351+
assert(markup.match(/<div class="A" [^>]*>mainpage/));
352+
});
353+
354+
it('does not override child classname', function() {
355+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/hasclassname" />);
356+
assert(markup.match(/<div class="ownClassname"/));
357+
});
358+
359+
it('passes childProps to contextual children, but inner childProps have priority', function() {
360+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/nested/foo" />);
361+
assert.equal(markup, '<div class="X"><div class="Y" data-own-prop="true" data-from-first="A">' +
362+
'<div class="B" data-from-first="A" data-from-second="B">foo</div></div></div>');
363+
});
364+
365+
it('keeps going and going', function() {
366+
var markup = ReactDOMServer.renderToStaticMarkup(<App path="/nested/nestedAgain/bar" />);
367+
assert.equal(markup, '<div class="X"><div class="Y" data-own-prop="true" data-from-first="A">' +
368+
'<div class="Z" data-own-prop="true" data-from-first="A" data-from-second="B">' +
369+
'<div class="C" data-from-first="A" data-from-second="B" data-from-third="C">bar' +
370+
'</div></div></div></div>');
371+
});
372+
});
373+
327374
});

0 commit comments

Comments
 (0)