I am having a hard time figuring out how to mount components inside a nested components with react router v1.0. I have an App component that loads a Layout component. The Layout component then loads two components, Menu and Content. I want to load different components inside the Content component based on the route.
Below is my sample code.
var App = React.createClass({
render : function(){
return <div><Layout/></div>
}
});
var Layout = React.createClass({
render : function(){
return(
<div>
<Menu/>
<Content/>
</div>
)
}
});
var Content = React.createClass({
render : function(){
return <div>This is where i want to mount my components</div>
}
});
var List = React.createClass({
render : function(){
return <div>some list goes here</div>
}
});
var Graph = React.createClass({
render : function(){
return <div>some graph goes here</div>
}
});
<Router>
<Route path='/' component={App}>
<Route path='/list' component={List}/>
<Route path='/graph' component={Graph}/>
</Route>
</Router>
Any help/pointers will be highly appreciated.
Thanks
It's all the same as basic React components. When you nest them, they're available on this.props.children. So you would end up with something like this:
var App = React.createClass({
render : function(){
return <div><Layout>{this.props.children}</Layout></div>
}
});
var Layout = React.createClass({
render : function(){
return(
<div>
<Menu/>
<Content>{this.props.children}</Content>
</div>
)
}
});
var Content = React.createClass({
render : function(){
return <div>{this.props.children}</div>
}
});
Related
I have this .js file that creates several divs and renders a few components and assigns them to a div. How can I use react router in this case if it only renders one component? Do I need to change this original file?
HomePage.jsx
import React from 'react';
import 'bootstrap-webpack';
import BigPic from './components/Jumbotron';
import Major from './components/Major';
import Footer from './components/Footer';
import GA from './components/GA';
var gA = require('react-google-analytics');
function googleAnalytics() {
var ga = document.createElement('div');
document.body.appendChild(ga);
React.render(<GA />, ga);
gA('create', 'UA-XXXX-Y', 'auto');
gA('send', 'pageview');
}
function jumbotron() {
//jumbotron
var wrapper = document.createElement('div');
//set jumbotron id and class
wrapper.id = "big";
wrapper.className = "site-wrapper";
//append div
document.body.appendChild(wrapper);
const jumbotron = document.getElementById('big');
React.render(<BigPic />, jumbotron);
}
function features() {
//features
var feature = document.createElement('div');
//set features id
feature.id= "featured-wrapper";
// append div to body
document.body.appendChild(feature);
const major = document.getElementById('featured-wrapper');
React.render(<Major />, major);
}
function footer() {
//footer
var bottom = document.createElement('footer');
//set footer id
bottom.id = 'footer';
bottom.className = "footer-basic-centered";
//append footer to bottom
document.body.appendChild(bottom);
const footer = document.getElementById('footer');
React.render(<Footer />, footer);
}
function homepage() {
jumbotron();
features();
footer();
googleAnalytics();
}
homepage();
Your main app will need to be changed to look something like this:
var routes = (
<Route handler={App} path='/'>
<Route name='major' handler={Major} path='major'>
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
You will then need to update App to include your jumbotron, footer and GA code:
React.createClass({
render: function(){
return (
<div>
<Jumbotron />
<RouteHandler />
<Footer />
<GA />
</div>
);
}
});
The key bit here is RouteHandler as this will render the subroute's component here.
The react class myComponent is not rendering inside the element example1.
what I am able to get in console is
You are using the in-browser JSX transformer. Be sure to precompile your JSX for production - http://facebook.github.io/react/docs/tooling-integration.html#jsx
code
<script type="text/jsx">
var myComponent = React.createClass({
render: function() {
return (
<h2>{this.props.name} wants to eat {this.props.food}</h2>
);
}
});
React.render(
<div>
<myComponent food="fruits" name="Raj1"/>
<myComponent food="Veggies" name="Raj2"/>
<myComponent food="Chicken" name="Raj3"/>
<myComponent food="Burger" name="Raj4"/>
</div>,
document.getElementById('example1'));
</script>
You must capitalize your react classes
var MyComponent = React.createClass({
render: function() {
return (
<h2>{this.props.name} wants to eat {this.props.food}</h2>
);
}
});
working fiddle example
I've followed a couple of examples in an attempt to get access to a parameter from a Route in the React component that handles it. However the result of console.log on this.props from inside the render or componentDidMount is always {} when I'd expect it to contain the gameId from the gamesView route.
client.js which starts the Router:
// HTML5 History API fix for local
if (config.environment === 'dev') {
var router = Router.create({ routes: routes });
} else {
var router = Router.create({ routes: routes, location: Router.HistoryLocation });
}
router.run(function(Handler) {
React.render(
<Handler />,
document.getElementById('app')
);
});
routes.js with some routes removed for simplicity:
var routes = (
<Route name='home' path='/' handler={app}>
<DefaultRoute handler={home} location="/" />
<Route name='gamesView' path='/games/:gameId' handler={gamesView} />
</Route>
);
module.exports = routes;
...and app.js which wraps the other routes, I've tried it both with and without {...this.props} in the RouteHandler. If I console.log(this.props) from inside the render function here is also returns {}:
var App = React.createClass({
render: function() {
return (
<div className='container'>
<div className='row'>
<RouteHandler {...this.props} />
</div>
</div>
);
}
});
module.exports = App;
Finally the gamesView React component that I expect to see the props object. Here this.props is also {} and the following results in the error TypeError: $__0 is undefined var $__0= this.props.params,gameId=$__0.gameId;:
var GamesView = React.createClass({
render: function() {
var { gameId } = this.props.params;
return (
<div>
<h1>Game Name</h1>
<p>{gameId}</p>
</div>
);
}
});
module.exports = GamesView;
Anybody have any ideas?
You won't see those params until you are at the component defined in your router. App won't know anything about them. If you put the console.log(this.props.params) in your gamesView component, however, you should see them.
After discussing on the React Router (RR) Github it turned out this was due to me using an older version of RR (v0.11.6).
Looking at the example in the docs for that release it showed that I needed to use a Router.State mixin and then get the expected param via var gameId = this.getParams().gameId;.
Without upgrading RR the working version of my original example for GamesView becomes:
var React = require('react');
var Router = require('react-router');
var { Route, RouteHandler, Link } = Router;
var GamesView = React.createClass({
mixins: [ Router.State ],
render: function() {
var gameId = this.getParams().gameId;
return (
<div>
<h1>Game Name</h1>
<p>{gameId}</p>
</div>
);
}
});
module.exports = GamesView;
Outer component (page layout):
var Layout = React.createClass({
render() {
return (
<div className="container">
<header>
<h1>{this.props.title}</h1>
</header>
<section>
{this.props.children}
</section>
</div>
);
}
});
Component one (page 1):
var PageOne = React.createClass({
render() {
return (
<Layout title="Component One">
<p>This is component one.</p>
</Layout>
);
}
});
Component two (page 2):
var PageTwo = React.createClass({
render() {
return (
<Layout title="Component Two">
<p>This is component two.</p>
</Layout>
);
}
});
Now, if we render these components to document.body dynamically, based on which page user is located (using HTML5 History API), how would that impact performance (as opposed to switching just Page components without re-rendering the outer (layout) component)?
var React = require('react');
var {Router} = require('director');
var render = (page) => { React.renderComponent(page(), document.body); };
var routes = {
'/page-one': () => { render(require('./pages/PageOne')); },
'/page-two': () => { render(require('./pages/PageTwo')); }
};
Router(routes).configure({html5history: true}).init();
P.S.: The HTML markup in these sample components is intentionally simplified. On StackOverflow.com example, there could be page components such as Questions, Tags, Users, Badges, AskQuestion, all contained inside a layout component which itself contains header, footer, navigation, sidebar.
You can't do this without rerender because this two page components are not similar. You can avoid lot of mutations if you create one component with some calculated parameters.
Component Page:
var Page = React.createClass({
render() {
return (
var _component_title = "Component" + this.props.componentName
<Layout title={_component_title}>
<p>This is {_component_title}</p>
</Layout>
);
}
});
Router:
var Page = require('./pages/Page')
var render = (page) => { React.renderComponent(Page({componentName: page}), document.body); };
var routes = {
'/page-one': () => { render('One'); },
'/page-two': () => { render('Two'); }
};
React will rerender only nodes with component name.
But you no need wory about rerender, React do this really fast.
I'm trying to pass some props down to my handled component however react-router fails to do so.
var Objects = React.createClass({
getInitialState: function() {
return {
selected: "All"
}
},
select: function(opt) {
this.setState({
selected: opt
});
},
render: function() {
return (
<div>
<LeftNav select={this.select} />
<this.props.activeRouteHandler selected={this.state.selected} />
</div>
);
}
});
var routes = (
<Routes>
<DefaultRoute name="objects" handler={objecctHandler}/>
</Routes>
);
The router loads fine as I can see '#/' in the url now. The Left nav renders fine and updates the state as well. however there is not props.selected in the handeded component namely objectHandler. Am I missing something here? Thanks.
I'm using react-router 0.7.0
Try making the DefaultRoute for 'objectHandler' a child of another route that defines 'Objects' as the handler. Such as:
var routes = (
<Routes>
<Route handler={Objects}>
<DefaultRoute name="objects" handler={objectHandler}/>
</Route>
</Routes>
);
jsfiddle: http://jsfiddle.net/gq1uym5y/1/
What I'm using right now is something like this.
One top level route that just routes to the App component:
React.renderComponent(
<Routes>
<Route handler={App}>
<Route path="/todos" handler={TodoList} />
</Route>
</Routes>
, mountNode);
The App component looks like this. I pass a Container to all subroutes (ie to the TodoList route). This container contains the list of todos (and anything else I need in the app, including methods for adding/persisting new Todos). This helps with keeping state at the top level and decouple subcomponents. Since the App component is used for every route, it never unmounts, so it doesn't loose its state.
var Container = function(app) {
return {
getTodos: function() {
return app.state.todos;
}
};
};
var App = React.createClass({
getInitialState: function() {
return {
todos: ['Buy milk', 'Call Mike']
};
},
componentWillMount: function() {
this.container = Container(this);
},
render: function() {
return <this.props.activeRouteHandler container={this.container} />;
}
});
Here's what the TodoList looks like. It's actually two components: TodoList and TodoListInner. The TodoListInner remains clean and testable. The TodoList itself is not so easily testable, but since it just wraps around the inner component, this shouldn't be much of a problem.
var TodoListInner = React.createClass({
render: function() {
<ul>
{this.props.todos.map(function(todo) {
return <li>{todo}</li>;
})}
</ul>
}
})
var TodoList = React.createClass({
render: function() {
<TodoListInner todos={this.props.container.getTodos()} />
}
});
It's a little more complex than jsmiff's solution but does the job with some added benefits.