How do I keep document.title updated in React app?
I wrote react-document-title just for that.
It provides a declarative way to specify document.title
in a single-page app.
If you want to get title on server after rendering components to string, call DocumentTitle.rewind()
.
Features
- Does not emit DOM, not even a
<noscript>
; - Like a normal React compoment, can use its parent's
props
andstate
; - Can be defined in many places throughout the application;
- Supports arbitrary levels of nesting, so you can define app-wide and page-specific titles;
- Works on client and server.
Example
Assuming you use something like react-router:
var App = React.createClass({ render: function () { // Use "My Web App" if no child overrides this return ( <DocumentTitle title='My Web App'> <this.props.activeRouteHandler /> </DocumentTitle> ); }});var HomePage = React.createClass({ render: function () { // Use "Home" while this component is mounted return ( <DocumentTitle title='Home'> <h1>Home, sweet home.</h1> </DocumentTitle> ); }});var NewArticlePage = React.createClass({ mixins: [LinkStateMixin], render: function () { // Update using value from state while this component is mounted return ( <DocumentTitle title={this.state.title || 'Untitled'}> <div> <h1>New Article</h1> <input valueLink={this.linkState('title')} /> </div> </DocumentTitle> ); }});
Source
I keep track of mounted instances and only use title
given to the top DocumentTitle
in the mounted instance stack whenever it updates, gets mounted or unmounted. On server, componentWillMount
fires but we won't get didMount
or willUnmount
, so we introduce DocumentTitle.rewind()
that returns a string and destroys state to prepare for next request.
var DocumentTitle = React.createClass({ propTypes: { title: PropTypes.string }, statics: { mountedInstances: [], rewind: function () { var activeInstance = DocumentTitle.getActiveInstance(); DocumentTitle.mountedInstances.splice(0); if (activeInstance) { return activeInstance.props.title; } }, getActiveInstance: function () { var length = DocumentTitle.mountedInstances.length; if (length > 0) { return DocumentTitle.mountedInstances[length - 1]; } }, updateDocumentTitle: function () { if (typeof document === 'undefined') { return; } var activeInstance = DocumentTitle.getActiveInstance(); if (activeInstance) { document.title = activeInstance.props.title; } } }, getDefaultProps: function () { return { title: '' }; }, isActive: function () { return this === DocumentTitle.getActiveInstance(); }, componentWillMount: function () { DocumentTitle.mountedInstances.push(this); DocumentTitle.updateDocumentTitle(); }, componentDidUpdate: function (prevProps) { if (this.isActive() && prevProps.title !== this.props.title) { DocumentTitle.updateDocumentTitle(); } }, componentWillUnmount: function () { var index = DocumentTitle.mountedInstances.indexOf(this); DocumentTitle.mountedInstances.splice(index, 1); DocumentTitle.updateDocumentTitle(); }, render: function () { if (this.props.children) { return Children.only(this.props.children); } else { return null; } }});module.exports = DocumentTitle;
class Layout extends React.Component { constructor(props){ super(props); document.title = this.props.title; } render(){ return( <div> </div> ); }}
and then <Layout title="My Title"/>
that easy!