Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I keep document.title updated in React app?

Since React doesn't have any builtin way to manage document.title, I used to set it inside componentDidMount of my route handlers.

However now I need to amend the title based on state fetched asynchronously. I started putting assingments into componentDidUpdate, but every now and then I forget to put document.title assignment into some pages, and previous title sticks around until I finally notice it.

Ideally I'd like a way to express document.title declaratively, without having to assign it. Some kind of “fake” component would probably be most convenient, given that I want to be able to specify the document title at several nesting levels:

  • On top level (the default title);
  • On page level (for some of the pages, but not all);
  • Sometimes, on inner component level (e.g. user typing into a field).

Additional requirements:

  • Title specified in child should override title specified by parent;
  • Reliable (guarantees cleanup on route change);
  • Should not emit any DOM (i.e. no hacks with component returning <noscript>);
  • I'm using react-router but it's better if this component works with other routers too.

Anything I can use?

like image 581
Dan Abramov Avatar asked Oct 08 '14 21:10

Dan Abramov


People also ask

How do you update a title in react JS?

As others have mentioned, you can use document. title = 'My new title' and React Helmet to update the page title.

Which hook could be used to update the documents title?

Q . Which Hook could be used to update the document's title? useEffect(function updateTitle() { document.


1 Answers

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 and state;
  • 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; 
like image 80
Dan Abramov Avatar answered Oct 02 '22 16:10

Dan Abramov