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

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?

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;

Take a look at the NFL's react-helmet.

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!

Try react-frozenhead, it's actually more sophisticated than react-document-title - it allows us change title, description and anything else in section.

Meanwhile, 3 years have gone! ;-)
If you want to manipulate other page headers than title (like description, canonical, etc.), react-document-meta NPM dependency could be a good thing to use.

Related

Exposing the state of a react widget

I have made a react UI widget thats let's the user select a number of different times and dates. The user's current selection is stored in the state of a top level component, DateTimePicker. I then have a widget wrapper like so:
import ...
export default {
new: (args) => {
const store = {
reactElement: <DateTimePicker
startDate={args.startDate}
endDate={args.endDate}
/>
};
return {
getState: () => {
return store.reactElement.getState(); // DOESN'T WORK
},
render: (selector) => {
ReactDOM.render(store.reactElement, document.querySelector(selector));
}
};
}
};
I want to add a validation to make sure that at least X days/times are selected, but this validation needs to be implemented outside of the widget.
For this, I'll need someway of asking the widget of it 's state. i.e. what has the user selected? Although it seems like the state of the class is not part of the public api of a react component.
How can I acess the state, or is there another way I'm missing?
The solution to doing things imperatively from the parent to the child usually involves getting a ref to the child component. Something along these lines:
export default {
new: (args) => {
let myRef = React.createRef();
const store = {
reactElement: <DateTimePicker
ref={myRef}
startDate={args.startDate}
endDate={args.endDate}
/>
};
return {
getState: () => {
return myRef.current.getState();
},
render: (selector) => {
ReactDOM.render(store.reactElement, document.querySelector(selector));
}
};
}
};
With ref={myRef} added as a prop, whenever DateTimePicker gets mounted, it will assign a reference to the mounted component to myRef.current. You can then use that reference to interact directly with the most recently mounted component.

use Waypoint with React to execute a function after scroll

I have a counter component that counts up to a number on the page. I need it to run ones the user scrolls to that point on the page. How do I do that with Waypoint? Or is there another solution?
import React, { Component } from 'react';
import Waypoint from 'react-waypoint';
var Counter2017 = React.createClass({
getInitialState: function() {
return {visionElapsed: 0};
},
tick: function() {
if (this.state.visionElapsed < 125) {
this.setState({visionElapsed: this.state.visionElapsed + 1});
}
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 15);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
<span>{this.state.visionElapsed}</span>
);
}
});
export default Counter2017;
You can try with componentDidUpdate(prevProps, prevState). In that method you will get notified about change in state. Invoke there what you need when user reaches the point.
More in docs:
https://facebook.github.io/react/docs/react-component.html#componentdidupdate
You insert your waypoint where you want it to be (it is a DOM element).
So you would have something that looks like this:
<div>
<AnyComponents />
<Waypoint
onEnter={() => this.interval = yourInterval}
/>
</div>
Of course you can check if there is already an interval so you don't override it. You can also set an onLeave props on the Waypoint component. I suggest you to read about it here: https://github.com/brigade/react-waypoint
The best you can do is using the react-waypoint lib

findDOMNode of mounted component in ReactJS

I have two JS files included in page as utility.js and utility1.js Code for utility.js
var HelloWorld = React.createClass({
render: function() {
return (
<p>
Hello, <input type="text" ref="mytestinput" placeholder="Your name here" />!<br />
It is {this.props.date.toTimeString()}
</p>
);
}
});
setInterval(function() {
React.render(
<HelloWorld date={new Date()} />,
document.getElementById('container')
);
}, 1000);
Code for utility1.js
var MyComponent = React.createClass({
handleClick: function() {
// Explicitly focus the text input using the raw DOM API.
React.findDOMNode(HelloWorld.refs.mytestinput).focus();
},
render: function() {
// The ref attribute adds a reference to the component to
// this.refs when the component is mounted.
return (
<div>
<input type="text" ref="myTextInput" />
<input
type="button"
value="Focus the text input"
onClick={this.handleClick}
/>
</div>
);
}
});
React.render(
<MyComponent />,
document.getElementById('container1')
);
The problem here is I want focus on input of HelloWorld Component of utility.js from utility1.js. I saw their is one method as findDOMNode for mounted components. But this code is not working for me. Can Somebody try this JS Fiddle here and let me know possible solution.
You need to create the global event system in order to allow both components communicate with each other if they are not in parent-child relationship. Here is more information about global event system
Here is the solution: jsfiddle
var CustomEvents = (function() {
var _map = {};
return {
subscribe: function(name, cb) {
_map[name] || (_map[name] = []);
_map[name].push(cb);
},
notify: function(name, data) {
if (!_map[name]) {
return;
}
// if you want canceling or anything else, add it in to this cb loop
_map[name].forEach(function(cb) {
cb(data);
});
}
}
})();
var HelloWorld = React.createClass({
componentDidMount: function() {
React.findDomNode(this.refs.mytestinput).focus()
},
...
});
or if your React.js is up-to-date, use this:
componentDidMount() {
this.refs.mytestinput.focus()
}
Refs are local to the component they are defined on, so HelloWorld.refs.mytestinput is not valid. Furthermore, since MyComponent and HelloWorld are part of two different React applications (created by two different calls to React.render), there's no built-in way to access the refs in HelloWorld from MyComponent. You would need to set some kind of global reference to the component, use message passing from one app to the other, emit events of some kind indicating the input should be focused, or use some other method of "global" communication.
Just use
this.refs.myTextInput
https://jsfiddle.net/e0cjqLu2/

Passing AJAX Results As Props to Child Component

I'm trying to create a blog in React. In my main ReactBlog Component, I'm doing an AJAX call to a node server to return an array of posts. I want to pass this post data to different components as props.
In particular, I have a component called PostViewer that will show post information. I want it to by default show the post passed in from its parent via props, and otherwise show data that is set via a state call.
Currently, the relevant parts of my code looks like this.
var ReactBlog = React.createClass({
getInitialState: function() {
return {
posts: []
};
},
componentDidMount: function() {
$.get(this.props.url, function(data) {
if (this.isMounted()) {
this.setState({
posts: data
});
}
}.bind(this));
},
render: function() {
var latestPost = this.state.posts[0];
return (
<div className="layout">
<div className="layout layout-sidebar">
<PostList posts={this.state.posts}/>
</div>
<div className="layout layout-content">
<PostViewer post={latestPost}/>
</div>
</div>
)
}
});
and the child component:
var PostViewer = React.createClass({
getInitialState: function() {
return {
post: this.props.post
}
},
render: function() {
/* handle check for initial load which doesn't include prop data yet */
if (this.state.post) {
return (
<div>
{this.state.post.title}
</div>
)
}
return (
<div/>
)
}
});
The above works if I swap out the if statement and content in my child's render to this.props.* However, this would mean that I couldn't change the content later via state, correct?
TLDR: I want to set a default post to be viewed via props in a child component (results of an AJAX call), and I want to be able to change what post is being viewed by adding onClick events (of another component) that will update the state.
Is this the correct way to go about it?
Current hierarchy of my app's components are:
React Blog
- Post List
- Post Snippet (click will callback on React Blog and update Post Viewer)
- Post Viewer (default post passed in via props)
Thanks!
EDIT:
So what I ended up doing was attaching the props in ReactBlog using a value based on this.state. This ensured that it updates when I change state and renders correctly in child components. However, to do this I had to chain onClick callbacks up through all the various child components. Is this correct? It seems like it could get VERY messy. Here's my full example code:
var ReactBlog = React.createClass({
getInitialState: function() {
return {
posts: [],
};
},
componentDidMount: function() {
$.get(this.props.url, function(data) {
if (this.isMounted()) {
this.setState({
posts: data,
post: data[0]
});
}
}.bind(this));
},
focusPost: function(slug) {
$.get('/api/posts/' + slug, function(data) {
this.setState({
post: data
})
}.bind(this));
},
render: function() {
return (
<div className="layout">
<div className="layout layout-sidebar">
<PostList handleTitleClick={this.focusPost} posts={this.state.posts}/>
</div>
<div className="layout layout-content">
<PostViewer post={this.state.post}/>
</div>
</div>
)
}
});
var PostList = React.createClass({
handleTitleClick: function(slug) {
this.props.handleTitleClick(slug);
},
render: function() {
var posts = this.props.posts;
var postSnippets = posts.map(function(post, i) {
return <PostSnippet data={post} key={i} handleTitleClick={this.handleTitleClick}/>;
}, this);
return (
<div className="posts-list">
<ul>
{postSnippets}
</ul>
</div>
)
}
});
var PostSnippet = React.createClass({
handleTitleClick: function(slug) {
this.props.handleTitleClick(slug);
},
render: function() {
var post = this.props.data;
return (
<li>
<h1 onClick={this.handleTitleClick.bind(this, post.slug)}>{post.title}</h1>
</li>
)
}
});
var PostViewer = React.createClass({
getInitialState: function() {
return {
post: this.props.post
}
},
render: function() {
/* handle check for initial load which doesn't include prop data yet */
if (this.props.post) {
return (
<div>
{this.props.post.title}
</div>
)
}
return (
<div/>
)
}
});
Still hoping to get some feedback / hope this helps!
This is an old question, but I believe still relevant, so I'm going to throw in my 2 cents.
Ideally, you want to separate out any ajax calls into an actions file instead of doing it right inside a component. Without going into using something like Redux to help you manage your state (which, at this point in time, I would recommend redux + react-redux), you could use something called "container components" to do all of the heavy state lifting for you and then use props in the component that's doing the main layout. Here's an example:
// childComponent.js
import React from 'react';
import axios from 'axios'; // ajax stuff similar to jquery but with promises
const ChildComponent = React.createClass({
render: function() {
<ul className="posts">
{this.props.posts.map(function(post){
return (
<li>
<h3>{post.title}</h3>
<p>{post.content}</p>
</li>
)
})}
</ul>
}
})
const ChildComponentContainer = React.createClass({
getInitialState: function() {
return {
posts: []
}
},
componentWillMount: function() {
axios.get(this.props.url, function(resp) {
this.setState({
posts: resp.data
});
}.bind(this));
},
render: function() {
return (
<ChildComponent posts={this.state.posts} />
)
}
})
export default ChildComponentContainer;
A blog is static for the most part, so you could exploit React immutable structures to "render everything" all the time instead of using the state.
One option for this is to use a router (like page.js) to fetch data.
Here is some code http://jsbin.com/qesimopugo/1/edit?html,js,output
If you don't understand something just let me know ;)
Get rid of isMounted and make use of context if you're passing callbacks down several levels

React waiting for map function before deciding render to prevent div flash

I am having a bit of trouble preventing a div flash where react will render the error message then receive props and finally render the props.
In my EventsView component I have the following.
view.js
var view;
if (_.size(this.props.events) !== 0) {
view = this.props.events.map(function(year) {
return <YearView year={year} />;
});
} else {
view = <NoEventsView />
}
So the variable view is rendered, initially there is no this.props.events on page load as I have another part of the file creating it. The moment this.props.events is created the events are rendered. This is done throught a react.Backbone mixin.
I have tried doing something used some of the component lifecycle methods such as componentWillUpdate. The problem is they don't seem to want to work the way I want them to work and the error message is never rendered. Example of what I have tried (among others) below
I thought of doing something like this.
view.js
componentWillUpdate: function(nextprops, nextstate) {
if (_.size(nextprops.events) !== 0) {
var view = nextprops.events.map(function(year) {
return <YearView year={year} />;
});
this.setState({
view: view
});
} else {
this.setState({
view: <NoEventsView />
})
}
},
// render this.state.view
I would then set the getInitialState:view to the a this.props.events map.
*EDIT - A temporary and terrible workaround *
So the problem I was having was I had a top level component accepting a single prop which was a global variable like so
view.js
React.render(<EventsView events={theseEvents} />, document.getElementById('mainContent'));
theseEvents is actually a Backbone Collection global variable initialized in another file.
On document load theseEvents would be unpopulated so my view would flash with the <NoEventView /> and then rerender when theseEvents is then populated. This is handled by the backbone mixin shown below.
Dan's solution pointed me in the right direction. I set a global variable window.hasEvents to true or false depending on what I got back from my ajax request but by that time React was already rendering (the component would start as hasEvents would be true, by the time it's finished hasEvents might be false). Even if I made hasEvents a prop of <EventsView /> react wouldn't rerender when it changed.
So as a temporary workaround I have defaulted hasEvents to true and rendered EventsView. I then waited for the component to load and checked whether it was in sync with hasEvents. shown below. Honestly this workaround is not very pleasant.
view.js
ar EventsView = React.createBackboneClass({
mixins: [
React.BackboneMixin("events", "all")
],
getInitialState: function() {
return {
hasEvents: window.hasEvents
}
},
componentDidMount: function() {
var model = this;
// This waits after component loading to check whether hasEvents is the same
setTimeout(function() {
if(!(model.state.hasEvents == window.hasEvents)) {
model.setState({
hasEvents: window.hasEvents
})
};
}, 1000);
},
render: function() {
var view;
if (this.props.events.length > 0) {
view = this.props.events.map(function(year) {
return <YearView year={year} />;
});
}
if (!this.state.hasEvents) {
view = <NoEventsView />
}
return {
// other parts of the page were also rendered here
{view}
}
});
essentially componentDidMount waits for a while after component mounting and then double checks a global variable. Definitely not a great workaround especially since it relies on a 1 second theseEvents population and it only works on the initial component mounting.
Your second approach is not “the React way” (although technically it may be working) because you shouldn't put React elements into state. Only data should go there, and in render if need to figure out how that data corresponds to DOM stucture. It's also best to avoid state unless absolutely necessary.
That being said, I'm not quite sure what you're trying to achieve. Assuming you want to prevent "No Events" flash before events come through, the only way your component can do that is by knowing that data is being fetched. If, in addition to events prop, you pass isLoading boolean prop, you can do that:
render: function () {
var isEmpty = this.props.events.length === 0;
if (isEmpty) {
return this.props.isLoading ?
<p>Loading...</p> : // note you can also return null here to render nothing
<NoEventsView />;
}
return this.props.events.map(function (year) {
return <YearView year={year} />;
});
}
Since I do a request right when my app starts to check if the user is Authenticated this is how I've been handling it within my components. The parent components state is linked to my store, and it passes down authenticated as a property.
I set the components hasUpdated state to false initially, since it will receive props from the parent I wait until componentWillRecieveProps is triggered and then set hasUpdated to true since it means our call has returned. Then it renders the elements it needs to.
Not sure if this is the best approach but it has worked for our needs so far.
var SomeComponent= React.createClass({
displayName: 'SomeComponent',
getInitialState: function() {
return {
hasUpdated : false
};
},
getDefaultProps: function() {
return {
authenticated : false
};
},
componentWillReceiveProps: function(nextProps) {
this.setState({ hasUpdated : true });
},
render: function() {
var AuthElement;
if(this.state.hasUpdated){
if(this.props.authenticated){
AuthElement= <div>Authenticated</div>;
}else{
AuthElement= <div>Not Authenticated</div>;
}
}
return (
{AuthElement}
);
}
});

Categories

Resources