Set document title on client and server side - reactjs

I want to know how you set the title of each page with ReactJS. And more, I use react-router-component and i want use same tech to set title to each page at server side by using React.renderComponentToString.
My current root Component :
module.exports = App = React.createClass
getInitialState: ->
return title: 'My title'
render: ->
<html lang="fr">
<head>
<link rel="stylesheet" href="/assets/css/main.css" />
<script src="/assets/js/bundle.js"></script>
<title>{#state.title}</title>
</head>
<body>
<div id="body-content">
<div id="main-container">
<Content path={#props.path} />
</div>
</div>
</body>
</html>
And my Content component :
Content = React.createClass
render: ->
<Locations id="main" className="App" path={#props.path}>
<Location path="/" handler={MainPage} />
<Location path="/users/:username" handler={UserPage} />
</Locations>

Top-level React component
var React = require('react');
var AppStore = require('../stores/AppStore');
var Application = React.createClass({
propTypes: {
path: React.PropTypes.string.isRequired,
onSetTitle: React.PropTypes.func.isRequired
},
render() {
var page = AppStore.getPage(this.props.path);
this.props.onSetTitle(page.title);
return (
<div className="container">
<h1>{page.title}</h1>
<div return <div dangerouslySetInnerHTML={{__html: page.body}}>;
</div>
);
}
});
module.exports = Application;
Client-side startup script (entry point)
var React = require('react');
var Dispatcher = require('./core/Dispatcher');
var Application = require('./components/Application');
Dispatcher.register((payload) => {
var action = payload.action;
if (action.actionType === ActionTypes.CHANGE_LOCATION) {
app.setProps({path: action.path});
}
});
var app = React.createElement(Application, {
path: window.location.pathname,
onSetTitle: (title) => document.title = title
}));
app = React.render(app, document.body);
More info: https://gist.github.com/koistya/24715d295fbf710d1e24
Demo Project: https://github.com/kriasoft/react-starter-kit

to pass the title to your App component server side, you have to pass it the same way as you're passing the path, i.e. as props and not state.
So first you'll need to change:
<title>{#state.title}</title>
To:
<title>{#props.title}</title>
Then in your backend pass the wanted title to the App component when instantiating it, like so:
var url = require('url');
var ReactAsync = require('react-async');
var App = require('./path/to/your/root-component.jsx');
// app = your express app:
app.get('*', function (req, res) {
var path = url.parse(req.url).pathname;
var title = getTitleFromPath(path); // Made up function to get title from a path..
ReactAsync.renderComponentToStringWithAsyncState(
App({path: path, title: title}),
function (err, markup) {
if (err) return res.send(500, err);
res.send('<!DOCTYPE html>\n' + markup);
}
);
});
Hope that helps!
As for setting the title client side I think your solution with setting the document.title probably is the best option.
Update
I've now tested the above and the title is set correctly, however React gets a different checksum for the server generated component and the client generated one, which is no good:
Uncaught Error: Invariant Violation: You're trying to render a component to the document using server rendering but the checksum was invalid
So actually you shouldn't use this solution!
Will have to play around a little more...

Related

Common-Js and Material-UI Icons. Understanding why Icon won't load. (React Js)

I'm attempting to run the following code:
"use strict";
var React = require('react');
var Router = require('react-router');
var Link = Router.Link;
var Material = require('material-ui');
var ThemeManager = new Material.Styles.ThemeManager();
var Colors = Material.Styles.Colors;
var dropdown = Material.Icons.NavigationArrowDropDown; //This icon cannot be found
var Home = React.createClass({
childContextTypes: {
muiTheme: React.PropTypes.object
},
getChildContext: function () {
return {
muiTheme: ThemeManager.getCurrentTheme()
};
},
componentWillMount: function () {
ThemeManager.setPalette({
accent1Color: Colors.cyan500
});
},
render: function () {
return (
<div>
<Material.AppBar title="Test" showMenuIconButton={false}>
</Material.AppBar>
<Material.List>
<Material.ListItem primaryText={"Queue"} leftIcon={<Material.Icons.NavigationChevronLeft/>} />
<Material.ListItem primaryText={"Log"} leftIcon={<Material.Icons.NavigationArrowDropDown/>} />
<Material.ListItem primaryText={"Settings"} />
</Material.List>
<Material.Paper>
<span>This is some text</span>
<Material.RaisedButton label="Super Secret Password" primary={true}/>
</Material.Paper>
</div>
);
}
});
module.exports = Home;
I've included the necessary packages and the code runs fine if I don't include
Material.Icons.NavigationArrowDropDown;
I've navigated to material-ui (0.11.1) and the file does exist there as an export in the following path:
lib > svg-icons > Navigation > Arrow_drop_down.js and the source code is as follows:
'use strict';
var React = require('react/addons');
var PureRenderMixin = React.addons.PureRenderMixin;
var SvgIcon = require('../../svg-icon');
var NavigationArrowDropDown = React.createClass({
displayName: 'NavigationArrowDropDown',
mixins: [PureRenderMixin],
render: function render() {
return React.createElement(
SvgIcon,
this.props,
React.createElement('path', { d: 'M7 10l5 5 5-5z' })
);
}
});
module.exports = NavigationArrowDropDown;
However, when compiling and running the application it cannot find the item and complains it does not exist, yet the other item
Material.Icons.NavigationChevronLeft
Gets found without issue. This file (with the exclusion of my router and app.js) are my entire project.
Since both files exist in the same folder, I cannot understand why the one reference is found and the other isn't?
The error occurs at runtime and jsLint doesnt pick it up. Additionally, when removing the listItem icon my page renders correctly. The problem appears to be tied directly to this component.
Additional Note: I have removed the var dropdown, it was there merely to demonstrate how the export is not being found from Material UI.
tl;dr : Material UI Icon class in the same folder as another Icon class is not being picked up. Why?
As you can see in src/index.js, NavigationArrowDropDown isn't being set on Material.Icons, while NavigationChevronLeft is. The component is used in other places, but is never publicly exposed through material-ui's main export.
However, you can still require it like you would any other file:
var NavigationArrowDropDown = require('material-ui/lib/svg-icons/navigation/arrow-drop-down');
Looking at the README, it looks like this is the recommended way to reach single components.

Switching between similar routes in React Router

I have this kind of route:
<Route name="itemDetails" handler={ItemDetails} path="/item/:itemId"/>
It leads to the page, containing full product description.
There are few "related items". Each of them contains the Link to the different "itemDetails" page. When I'm clicking on one of this links, the route is changing in browser address bar, but the content is not.
Why? The rest of routes works well.
Also, if i reload the page, content is refreshing and it's mutching to the route in browser address bar
It might be because of the incorrect url listener
It tried this:
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.getElementById('example'));
});
and this:
Router.run(routes, Router.HashLocation, function (Root) {
React.render(React.createElement(Root, null), document.body);
});
Here the itemDetails code:
var React = require('react');
var Store = require('../../stores/app-store');
var Preloader = require('../template/app-preloader');
var NewEstateItem = require('./app-newestateitem');
var NewEstateRelated = require('./app-newestaterelated');
var NewEstateDetails = React.createClass({
getInitialState: function() {
return { item: null };
},
componentWillMount: function() {
itemId = this.props.params.itemId;
console.log('ItemId:', itemId);
Store.getNewEstateById(itemId,this._getNewEstateDetails);
},
render: function() {
var item = this.state.item;
if(!item) {
return (<Preloader />);
}
return (
<div className="row">
<div className="container flex-container">
<div className="col s12 m12 l8">
<div className="col s12">
<h4>{item.Name}</h4>
</div>
<div className="col l4 hide-on-med-and-down margintop">
<h5 className="center">Related items</h5>
<div className="col s12">
<NewEstateRelated Price={item.Price}/>
</div>
</div>
</div>
</div>
);
},
_getNewEstateDetails: function (item) {
this.setState({item:item});
}
});
module.exports = NewEstateDetails;
And here relatedItem Link code:
<Link to="ItemDetails" params={{itemId: item.objectId, CustomID: item.CustomID}} className="btn</Link>
Here the store:
var getNewEstateData = {
getNewEstateById: function(objectId, callback) {
console.log('store.objectId', objectId);
function NewEstateData() {};
var item = Backendless.Persistence.of( NewEstateData ).findById( objectId );
console.log("store.getNewEstateById", item);
callback(item);
},
getAllNewEstate: function(callback) {
function NewEstateData() {};
var items = Backendless.Persistence.of( NewEstateData ).find().data;
callback(items)
},
getRelatedNewEstateItems: function (priceBottom, priceTop, callback) {
function NewEstateData() {};
var items = Backendless.Persistence.of( NewEstateData );
var dataQuery = {
condition: "Price >= "+priceBottom
}
var query = items.find( dataQuery ).data
var output = query.slice(0,3)
console.log('relatedItems', query);
callback(output)
}
};
module.exports = getNewEstateData;
I think this is the issue:
Your router is expecting :id:
<Route name="itemDetails" handler={ItemDetails} path="/item/:id"/>
But you are sending itemId in the <link>:
<Link to="ItemDetails" params={{itemId: item.objectId, CustomID: item.CustomID}} className="btn</Link>
I am pretty sure it should match and be:
<Link to="ItemDetails" params={{id: item.objectId, CustomID: item.CustomID}} className="btn</Link>
See the docs for it here where the example confirms this:
params: An object of the names/values that correspond with dynamic segments in your route path.
Docs Example
// given a route config like this
<Route name="user" path="/users/:userId"/>
// create a link with this
<Link to="user" params={{userId: "123"}}/>
// though, if your user properties match up to the dynamic segements:
<Link to="user" params={user}/>

Reflux store data rendering with line breaks and tag interpolation ES5

I do not want to use Babel/ES6 yet because reasons. I have been watching the egghead.io videos on react/reflux and have a component here I am rendering. It connects to the randomuser API and pulls 10 users into the store, when the store is updated it renders the user data onto the page. With the ES6 code shown in the video it allows nice interpolation of tags, so that but in my case I am just using lodash as _.map which operates slightly differently, and I am unable to find a way to render tag interpolation or even line breaks, as React renders out the elements as all children of one parent tag contained inside its own span tags.
The rendered code looks like this:
and my code is here:
var React = require('react');
var Reflux = require('reflux');
var request = require('superagent');
var _ = require('lodash');
var store = Reflux.createStore({
data: {users:[]},
init: function(){
request
.get("http://api.randomuser.me/?results=10")
.end(function(err,res){
if(err){
console.log(err)
}else {
var FirstName = res.body.results[0].user.name.first;
var LastName = res.body.results[0].user.name.last;
var picture = res.body.results[0].user.picture.thumbnail;
store.trigger({users:res.body.results})
}
});
},
getInitialState(){
return this.data;
}
});
var Name = React.createClass({
mixins:[Reflux.connect(store)],
render: function(){
return(
<div>
{_.map(this.state.users,function(n){
fName=n.user.name.first
lName=n.user.name.last
picture = n.user.picture.thumbnail;
return ""+fName+" "+lName + " " + picture
})
}
</div>
)
}
});
React.render(<Name />, document.getElementById('users'));
Any suggestions or advice would be greatly appreciated! also the egghead.io videos are top notch, i must give credit where it is due!
Personally, I try to avoid doing interpolation in JSX tags. JSX gives you a pretty solid API for constructing DOM elements! In this case, I'd do something like this:
render: function() {
var userElements = _.map(this.state.users,function(n){
var fName=n.user.name.first
var lName=n.user.name.last
var pictureURL = n.user.picture.thumbnail;
return (
<div className='user'>
<span className='first-name'>{fname}</span>
<span className='last-name'>{lname}</span>
<img className='picture' src={pictureURL}></img>
</div>
)
})
return (
<div className='user-container'>
{userElements}
</div>
)
}

ReactJS Invalid Checksum

I keep getting the following error when attempting server side rendering using ReactJS & node.
React attempted to use reuse markup in a container but the checksum was invalid.
I've seen an answer that passing the same props on the server and client resolves this issue. In my example, I don't have any props, so I'm not sure that the answer applies to my problem.
You can view my full example on my github account.
I'm including the important code below. Any insight is greatly appreciated.
JSX
/** #jsx React.DOM */
var React = require('react');
var index = React.createClass({
render: function() {
return (
<html>
<head>
<script src="bundle.js"></script>
</head>
<body>
<div>
hello
</div>
</body>
</html>
);
}
});
if (typeof window !== "undefined") {
React.renderComponent(<index/>, document.documentElement);
} else {
module.exports = index;
}
Server
require('node-jsx').install();
var express = require('express'),
app = express(),
React = require('react'),
index = require('./index.jsx');
var render = function(req, res){
var component = new index();
var str = React.renderComponentToString(component);
res.set('Content-Type', 'text/html');
res.send(str);
res.end();
}
app.get('/',render);
app.use(express.static(__dirname));
app.listen(8080);
Change
React.renderComponent(<index/>, document.documentElement);
to
React.renderComponent(<index/>, document);
and the notice goes away.

React CCSTransitionGroup animation not applying enter/leave classes

I'm trying to write a simple page slider. Here, when I click a page, it creates a new Page with random content, and re-renders the App component. On App render(), instead of the TransitionGroup holding both state.pages until animation completes, it just switches out the pages, never attaching the enter-leave classes and not performing the css animation. I'm sure I'm messing something up in the LifeCycle, but can't think of it.
Thanks for looking!
var Page = React.createClass({
handleClick: function(){
var pgs = ['page-one','page-two','page-three','page-four']
currentIdx = Math.floor(Math.random() * pgs.length);
var pg = pgs[ currentIdx ];
var newPg = <Page html={pg} title={'Title for ' + pg} />;
React.renderComponent(<App newPage={newPg} />, document.body)
},
render: function(){
return (<div className="content" style={{paddingTop: 44}} onClick={this.handleClick}>{this.props.html}</div>);
}
})
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var App = React.createClass({
getInitialState: function() {
return {pages: [<Page html="initial page" title="initial title" />]};
},
componentWillMount: function(){
this.setState({pages: [this.props.newPage]})
},
componentWillReceiveProps: function(nextProps) {
this.setState({pages: [nextProps.newPage]});
},
render: function() {
var title = this.state.pages.length ? this.state.pages[ this.state.pages.length - 1 ].props.title : 'none';
return (
<div id="body">
<TitleBar title={title} />
<ReactCSSTransitionGroup transitionName="pg" id="transdiv" component={React.DOM.div}>
{this.state.pages}
</ReactCSSTransitionGroup>
</div>
);
}
});
The problem was that I was setting the Page keys in Page.render() (not shown above), and not in App.render() I'm not sure why you can't set keys in the child/owned component as long as they're unique, but this fixed my problem.
var App = React.createClass({
// other methods are same
render: function(){
var title = 'Title';
var pgs = this.state.pages.map(function(pg){
// SET KEY HERE
pg.props.key = pg.props.title;
return pg;
}
return (
<div id="body">
<TitleBar title={title} />
<ReactCSSTransitionGroup transitionName="pg" id="transdiv" component={React.DOM.div}>
{pgs}
</ReactCSSTransitionGroup>
</div>
);
}
}
Also, if anyone can tell me the correct way to set props on unmounted components, please tell me. Setting them directly works, but it doesn't feel right.

Resources