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
Related
I know you can pass down states and props in React from a parent component to a child component, but is there any way to do this the opposite way?
For example:
Given some child component:
var Child = React.createClass({
getInitialState: function(){
return {
data: ''
};
},
componentDidMount: function(){
this.setState({data: 'something'});
},
render: function() {
return (
<div>
...
</div>
);
}
});
and given some parent component:
var Parent = React.createClass({
render: function() {
return (
<div>
<Child />
...
</div>
);
}
});
Is there any way for me to give Parent the value of the state data from Child?
No.
But yes. But really no.
You cannot "pass" anything from a child to a parent in React. However, there are two solutions you can use to simulate such a passing.
1) pass a callback from the parent to the child
var Parent = React.createClass({
getInitialState: function() {
return {
names: []
};
},
addName: function(name) {
this.setState({
names: this.state.names.push(name)
});
},
render: function() {
return (
<Child
addName={this.addName}
/>
);
}
});
var Child = React.createClass({
props: {
addName: React.PropTypes.func.isRequired
},
handleAddName: function(event) {
// This is a mock
event.preventDefault();
var name = event.target.value;
this.props.addName(name);
},
render: function() {
return (
...
onClick={this.handleAddName}
...
);
}
});
The second option is to have a top-level state by using a Flux-style action/store system, such as Reflux or Redux. These basically do the same thing as the above, but are more abstract and make doing so on much larger applications very easy.
One way to do this is through a 'render props' pattern I was recently introduced to. Remember, this.props.children is really just a way for React to pass things down to a component.
For your example:
var Parent = React.createClass({
render: function() {
return (
<div>
<Child>
{(childState) => {
// render other 'grandchildren' here
}}
</Child>
</div>
);
}
});
And then in <Child> render method:
var Child = React.createClass({
propTypes: {
children: React.PropTypes.func.isRequired
},
// etc
render () {
return this.props.children(this.state);
}
});
This is probably best suited for cases where the <Child /> is responsible for doing something but doesn't really care much at all about the children that would be rendered in its place. The example the react training guys used was for a component that would fetch from Github APIs, but allow the parent to really control what / if anything was rendered with those results.
I have a react component that gets a prop from another parent component. I checked in react developer tools, and the prop is for sure getting passed.
Here is my code:
var Post = React.createClass({
getInitialState: function () {
return { content: this.props.content };
},
rawMarkup: function() {
var rawMarkup = marked(this.state.content, {sanitize: true});
return { __html: rawMarkup };
},
render: function() {
return (
<div>
{this.props.content }
<div dangerouslySetInnerHTML={ this.rawMarkup() } />
</div>
);
}
});
This results in the error: Uncaught TypeError: Cannot read property 'replace' of undefined for marked.js. However, when I setInitialState to return { content: "Blah" }; it works fine. So it looks like the prop is not set there?
But when I do the {this.props.content} in the render, it works fine?
It's just that your state is out of date. Try adding this:
getInitialState: function () {
return { content: this.props.content || '' };
},
componentWillReceiveProps: function(nextProps) {
if (this.props.content !== nextProps.content) {
this.setState({
content: nextProps.content || '',
});
}
},
Read more about components' lifecycle here.
Edit: This will solve your problem, but generally using state this way is an anti-pattern (unless content is an input or something, you haven't mentioned that in your question). What you should do instead is create a new component that will only accept content prop and render marked output. I suggest you use a stateless functional component here.
var MarkedContent = (props) => {
return <div dangerouslySetInnerHTML={{__html: marked(props.content || '', {sanitize: true})}}></div>
}
Drop this component inside your Post component like this:
var Post = React.createClass({
render: function() {
return (
<div>
<MarkedContent content={this.props.content} />
</div>
);
}
});
Thanks David Walsh!
You don't have to synchronize props with state, even more using props in state is anti-pattern. render() is called each time when props or state changed
However, it's not an anti-pattern if you make it clear that
synchronization's not the goal here
var Post = React.createClass({
rawMarkup: function() {
var rawMarkup = marked(this.props.content, {sanitize: true});
return { __html: rawMarkup };
},
render: function() {
return (
<div>
{this.props.content }
<div dangerouslySetInnerHTML={ this.rawMarkup() } />
</div>
);
}
});
Do all your Post's have content?
I guess you are getting the list of posts from somewhere (a database) and for some of them the content is undefined, hence the:
Uncaught TypeError: Cannot read property 'replace' of undefined
Probably this.props.content has undefined as value. Then, this.state.content is initialized to undefined and when you call marked(this.state.content, {sanitize: true}) you get this error because you are passing an undefined to marked.
I have an array of objects which is passed as a property to a list that maps them to <li>.
I would like to be able, for any individual item, to click on an item from the list, and receive that object and then assign it to the root component's state - so I could then pass it on to another child comp.
var Menu = React.createClass({
render: function() {
return (<ul>
{
this.props.posts.map(function(post){
return <li><a onClick={function(e){console.log(e)}}>{post.title}</a></li>
})
}
</ul>)
}
})
https://jsfiddle.net/nbenita/yxw1z42q/
Thanks!
Pass a callback function into your Menu component as a prop and use Function.prototype.bind() to partially apply the relevant post object as an argument:
Updated fiddle: https://jsfiddle.net/yxw1z42q/2/
var Blog = React.createClass({
getInitialState: function() {
return {
selectedPost:this.props.posts[0]
};
},
onPostSelected: function(selectedPost) {
this.setState({
selectedPost: selectedPost
});
}
render: function() {
return (<div>
<Menu posts={this.props.posts} onClick={this.onPostSelected} />
<Post content={this.state.selectedPost} />
</div>)
}
})
var Menu = React.createClass({
render: function() {
return (<ul>
{
this.props.posts.map(function(post){
return <li><a onClick={this.props.onClick.bind(this, post)}>{post.title}</a></li>
}, this)
}
</ul>)
}
})
Further reading
React Docs - Communicate Between Components
react-training - Simple Component Communication
I have the next component 'Father' that contains a 'Children' component in React js.
var Father = React.createClass({
render: function () {
return (
<div>
<Children/>
</div>
);
},
onUpdate: function(state) {
this.setState(state);
} });
I want to call the onUpdate function on the father from the children BUT without calling the 'Children' method 'componentDidUpdate' because I'm using that method for some other thing that breaks my application.
How can I do that?
Pass it down in properties. If you need to update only specific parts and prevent your children from updating, use the method shouldComponentUpdate
var Father = React.createClass({
render: function () {
return (
<div>
<Children onUpdateCallback={this.onUpdate}/>
</div>
);
},
onUpdate: function(state) {
this.setState(state);
}
});
var Child = React.createClass({
render: function () { ... },
shouldComponentUpdate: function (prevProps, prevState) { ... return false}
});
If your Children can't/shouldn't update while the Parent does, I think you are probably doing something wrong, but good luck.
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.