React / Rails : Append dynamically element to DOM - ruby-on-rails

Currently following facebook tutorial on React (react_tuto).
I don't understand how 2 components can communicate so that on "submit a comment button" it appends dynamically the "comment list".
currently, comment are created on server but appears on page only when page refreshed
how can the comment appear on submit button?
This i my AddComment component
var AddComment = React.createClass({
getInitialState: function(){
return {
content: this.props.content,
adrien: "before"
}
},
handleKeyUp: function(e) {
this.setState({
content: this.refs.addComment.getDOMNode().value,
})
},
handleValidation: function() {
var that = this
$.ajax({
type: "POST",
data: {comment: { content: that.state.content } },
url: Routes.create_comment_path({format: 'json'}),
success: function(data) {
that.setState({
content: "",
adrien: "after"
})
}
})
},
render: function(){
return (
<div>
<textarea onKeyUp={this.handleKeyUp} value={this.state.value} ref="addComment"></textarea>
<button onClick={this.handleValidation}>submit</button>
</div>
)
}
})
This is my CommentList component:
var CommentList = React.createClass({
render: function() {
return (
<div>
{this.props.comments.map(function(comment){
return <CommentListElement key={comment.id} comment={comment} />;
})}
</div>
);
}
});

You need a common parent component for communication between different components.
I have updated you example a bit to include common parent component CommentSystem
Note: I have removed ajax call to just show the communication between component.
Check below link.
https://jsfiddle.net/j4yk3pzc/15/
Extra Info:
In react we store states on parent component and pass them down to children. Along with state we also pass actions to manipulate data down to the children. When child component want's to update data passed to it from parent, then it fires the action passed from the parent. This is called Data down action up approach. Data is passed from parent to child to grandchild. While actions are propagated from grandchild to child to parent.
If you don't want to create the parent component then you can use some Publish / Subscribe or EventEmitter based system to communicate between children having no common parent.
Reference:
http://ctheu.com/2015/02/12/how-to-communicate-between-react-components/
Code:
var CommentSystem = React.createClass({
getInitialState: function() {
return {
comments: []
}
},
addComments: function(comment) {
var comments = this.state.comments;
comments.push(comment);
this.setState({comments: comments})
},
render: function() {
return (
<div>
<AddComment addComments={this.addComments}/>
<CommentList comments={this.state.comments}/>
</div>
)
}
})
var AddComment = React.createClass({
getInitialState: function(){
return {
content: this.props.content,
adrien: "before"
}
},
handleKeyUp: function(e) {
this.setState({
content: this.refs.addComment.getDOMNode().value,
})
},
handleValidation: function() {
var that = this;
this.props.addComments(this.state.content);
},
render: function(){
return (
<div>
<textarea onKeyUp={this.handleKeyUp} value={this.state.value} ref="addComment"></textarea>
<button onClick={this.handleValidation}>submit</button>
</div>
)
}
})
var CommentList = React.createClass({
render: function() {
return (
<div>
{this.props.comments.map(function(comment){
return <CommentListElement key={comment.id} comment={comment} />;
})}
</div>
);
}
});
var CommentListElement = React.createClass({
render: function() {
return (
<div>{this.props.comment}</div>
)
}
})
React.render(<CommentSystem/>, document.getElementById('container'));
Hope this helps.

Related

why the function in side of function wont get called?

i have the following reactjs code to generate two dropdown list where the ddlproducts gets loaded by ddlCategories selection. but when i called the function getDataById() and tried to print the ajax populated array data2 to alert(), there was no alert() there were two alerts none of the alerts were prompted. it shown this error message on the IE console,
execution did not reached the function getDataById() 'cus the alert() in that function even didn't execute
SCRIPT438: Object doesn't support property or method 'getDataById'
correction: once the calling of this.props.getDataById() was changed to this.getDataById() it worked
but how do populate the ddlProducts dropdown. how do i access tag of the ddlProducts and then add the options to it?
here is the code:
var gdata=[];
var trStyle = {
'color': 'black',
'border-style' :'solid',
'margin-left':'20%'
};
var HCOMP = React.createClass({
getInitialState:function(){
return{data1:[], data2:[], isMounted:false, selectedValue:0}
},
componentDidMount:function(){
this.getData();
this.setState({isMounted:true})
},
ddlProdCatsChanegeEvent: function(e) {
if (this.state.isMounted)
{
var newV = ReactDOM.findDOMNode(this.refs.refProdCats).value;
var seleValue = newV;
this.setState({selectedValue:newV}, function(){
this.getDataById(this.state.selectedValue);
alert(this.state.data2);
});
}
},
render: function() {
var prodCats = this.state.data1.map(function(ele, index){// <PRODCATSOPTION optValue={ele.ProductCategoryID} optText={ele.Name} />
return <option value={ele.ProductCategoryID} data-key={index}>{ele.Name}</option>
});
prodCats.unshift(<option value={''}>{'---- select category ------'}</option>)
return (<div>Prodcut Category:<br /><select id="ddlCategories" ref="refProdCats" onChange={this.ddlProdCatsChanegeEvent}>{prodCats}</select><br />
Products:<br /><select id="ddlPorducts" ref="refProds"></select><br /></div>
)
},
getDataById:function(catId){
var x = catId;
alert('rec id:'+x);
$.ajax({
url:'http://localhost:53721//Home/GetProductCats?id='+ x,
method:'GET',
success:function(d1){
this.setState({data2:d1});
}.bind(this),
error:function(){
alert('ERROR');
}.bind(this)
})
},
getData:function(){
//ajax here
$.ajax({
url:'http://localhost:53721//Home/GetProductCats',
method:'GET',
success:function(d1){
this.setState({data1:d1});
}.bind(this),
error:function(){
alert('ERROR');
}.bind(this)
})
}
});
var PRODOPTIONS = React.createClass({
render:function(){
return(<option value={this.props.optValue}>{this.props.optText}</option> )
}
});
var PRODCATSOPTION = React.createClass({
render:function(){
return(<option value={this.props.optValue}>{this.props.optText}</option> )
}
});
ReactDOM.render( <HCOMP/>, document.getElementById('d1') );
Try updating ddlProdCatsChanegeEvent to ddlPropCatsChangeEvent.

how to avoid key/id problems in reactjs and make props pass from parent to child?

I keep hitting a wall when trying to get the parent data passed down to the child component.
My view:
<%= react_component 'Items', { data: #items } %>
My Items component makes an ajax call, sets state, and renders Item. Leaving key={this.props.id} out of the Item instance passed into the mapping function makes it so that the component html renders to the page. But add the key in, and I get a console error: Uncaught TypeError: Cannot read property 'id' of undefined
Here's 'Items':
var Items = React.createClass({
loadItemsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.loadItemsFromServer();
},
render: function() {
var itemNodes = this.props.data.map(function() {
return (
<Item key={this.props.id} />
);
});
return (
<div className="ui four column doubling stackable grid">
{itemNodes}
</div>
);
}
});
My item.js.jsx component just formats each Item:
var Item = React.createClass({
render: function() {
return (
<div className="item-card">
<div className="image">
</div>
<div className="description">
<div className="artist">{this.props.artist}</div>
</div>
</div>
);
}
});
The React dev tools extension shows the props and state data inside Items. The children, however, are empty.
I'm aware of this, but I'm setting key with this.props.id. I'm not sure what I'm missing?
I found a couple of problems with the code you posted, in the Items component
You're rendering this.props.data while in fact this.state.data is the one being updated with the ajax request. You need to render this.state.data but get the initial value from props
The map iterator function takes an argument representing the current array element, use it to access the properties instead of using this which is undefined
The updated code should look like this
var Item = React.createClass({
render: function() {
return (
<div className="item-card">
<div className="image">
</div>
<div className="description">
<div className="artist">{this.props.artist}</div>
</div>
</div>
);
}
});
var Items = React.createClass({
getInitialState: function() {
return {
// for initial state use the array passed as props,
// or empty array if not passed
data: this.props.data || []
};
},
loadItemsFromServer: function() {
var data = [{
id: 1,
artist: 'abc'
}, {
id: 2,
artist: 'def'
}]
this.setState({
data: data
});
},
componentDidMount: function() {
this.loadItemsFromServer();
},
render: function() {
// use this.state.data not this.props.data,
// since you are updating the state with the result of the ajax request,
// you're not updating the props
var itemNodes = this.state.data.map(function(item) {
// the map iterator function takes an item as a parameter,
// which is the current element of the array (this.state.data),
// use (item) to access properties, not (this)
return (
// use key as item id, and pass all the item properties
// to the Item component with ES6 object spread syntax
<Item key={item.id} {...item} />
);
});
return (
<div className="ui four column doubling stackable grid">
{itemNodes}
</div>
);
}
});
And here is a working example http://codepen.io/Gaafar/pen/EyyGPR?editors=0010
There are a couple of problems with your implementation.
First of all, you need to decide: Do you want to render the #items passed to the Items component from your view? Or do you want to load them asynchronous?
Because right now I get the impression you are trying to do both...
Render items passed from view
If you want to render the items from your view passed to the component, make sure it's proper json. You might need to call 'as_json' on it.
<%= react_component 'Items', { data: #items.as_json } %>
Then, in your Component, map the items to render the <Item /> components. Here is the second problem, regarding your key. You need to define the item variable to the callback function of your map function, and read the id from it:
var itemNodes = this.props.data.map(function(item) {
return (
<Item key={item.id} artist={item.artist} />
);
});
Note, I also added the author as prop, since you are using it in your <Item /> Component.
You can remove your componentDidMount and loadItemsFromServer functions, since you are not using them.
Load items asynchronous
If you want to load the items asynchronously, like you are trying to do in your loadItemsFromServer function, first of all, pass the url from your view and remove the {data: #items} part, since you will load the items from your component, something like:
<%= react_component 'Items', { url: items_path(format: :json) } %>
If you want to render the asynchronous fetched items, use:
var itemNodes = this.state.data.map(function(item) {
return (
<Item key={item.id} artist={item.artist} />
);loadItemsFromServer
});
Note I changed this.props.map to this.state.map
You can now use your componentDidMount and loadItemsFromServer functions to load the data and save them to state.

How to mount react_component from ajax html in rails

I use this in ruby on rails with react-rails.
In my app ,I want to use ajax to get a html response:
<%= react_component('Post') %>
Post Component:
var Component = React.createClass({
render:function(){
return <div>Article</div>;
}
})
and attach it to App Component:
var App = React.createClass({
getInitialState:function(){
return {html:''};
},
componentDidMount:function(){
var that = this;
$.get('/home/component').done(function(data){
that.setState({html:data});
});
},
getHtml:function(){
return { __html:this.state.html};
},
render: function() {
return <div dangerouslySetInnerHTML={this.getHtml()}></div>;
}
});
But after that it is fail, which show:
<div data-react-class="Post" data-react-props="{}"></div>
I already add post.js app.js to index.html.
How can I mount this to DOM.
Thank.....

I have a Rails app using React posting to my db using an ajax call. How do I handle the redirect?

So I have my Rails app trying to submit a cohort to the database. I can get it to post to the database but I do not know how to handle the redirect.
var NewCohort = React.createClass ({
getInitialState: function() {
return {name:'',description:''}
},
handleNameChange: function(e) {
this.setState({name:e.target.value})
},
handleDescriptionChange: function(e) {
this.setState({description:e.target.value})
},
handleSubmit: function(e) {
var that = this
$.ajax({
url: '/cohorts',
method: 'POST',
data: {
name: that.state.name,
description: that.state.description
},
success: function(data, success, xhr) {
console.log(data)
}
})
},
render: function() {
return (
<div className="container">
<h3>Create a new cohort</h3>
<form onSubmit={this.handleSubmit}>
<input type="text" value={this.state.name} onChange={this.handleNameChange}/>
<input type="text" value={this.state.description} onChange={this.handleDescriptionChange}/>
<input type="submit"/>
</form>
</div>
)
}
})
Is my ajax call which I send to my controller which has
def create
#cohort = Cohort.create(name: params[:name], description: params[:description])
render component: 'ShowCohort', props: { cohort: #cohort }
end
Which in my ajax success function, the data is the new React html code. How to I actually redirect the the page?
This is my React component to render
var ShowCohort = React.createClass ({
render: function() {
return (
<div className="container">
<h3>Hello {this.props.cohort.name}</h3>
<p>{this.props.cohort.description}</p>
</div>
)
}
})
Don't use ajax. Just use a regular form.
this should be like this
success: function(data, success, xhr) {
window.location.href = "redirect_url";
}
you can pass it as json as well like this in your controller
render json: {"redirect":true,"redirect_url": apps_path}, status:200
success: function(data, success, xhr) {
window.location.href = data.redirect_url;
}

Linked jQuery sortable lists and Backbone collections

I'm still finding my way with Backbone and I've always use Prototype instead of jQuery in the past so please forgive me if I'm doing something stupid.
I'm trying to develop a UI containing several connected unordered lists where each sortable list is represented by a separate Backbone collection. I'm using ICanHaz and Mustache templates but that's not of importance for my question.
When dragging items between the lists, how would I best achieve the automatic updating of the collections (remove a model from one and insert it into another)? I'm currently trying to use the receive and remove methods in the jQueryUI Sortable interaction — am I at least on the right lines?
var WS = {};
(function(ns) {
ns.Item = Backbone.Model.extend();
ns.Content = Backbone.Collection.extend({
model: ns.Item,
url: location.href,
initialize: function(el) {
this.el = $(el);
this.deferred = this.fetch();
},
recalculate: function() {
var count = this.length;
this.el.next(".subtotal").html(count);
},
setOrder: function() {
$.ajax({
url: this.url + "/reorder",
type: "POST",
data: "tasks=" + $(this.el).attr("id") + "&" + this.el.sortable("serialize")
});
}
});
ns.ContentRow = Backbone.View.extend({
tagName: "li",
className: "item",
events: {
"click .delete": "destroy"
},
initialize: function(options) {
_.bindAll(this, "render", "destroy");
this.model.bind("change", this.render);
this.model.view = this;
},
render: function() {
var row = ich.item(this.model.toJSON());
$(this.el).html(row);
return this;
},
destroy: function() {
if (confirm("Really delete?")) {
this.model.destroy({
success: function(model, response) {
$(model.view.el).remove();
},
error: function(model, response) {
console.log(response);
}
});
}
}
});
ns.ListView = Backbone.View.extend({
initialize: function(collection) {
this.el = collection.el;
this.collection = collection;
this.collection.bind("add", this.addOne, this);
_.bindAll(this, "addOne");
this.el.sortable({
axis: "y",
connectWith: ".tasks",
receive: _.bind(function(event, ui) {
// do something here?
}, this),
remove: _.bind(function(event, ui) {
// do something here?
}, this),
update: _.bind(function(event, ui) {
var list = ui.item.context.parentNode;
this.collection.setOrder();
}, this)
});
},
insert: function(item) {
var prefix = this.el.parentsUntil('ul').parent().attr("id"),
view = new ns.ContentRow({
model: item,
id: prefix + "_" + item.id
});
this.el.append(view.render().el);
},
addOne: function(item) {
if (item.isNew()) {
item.save({}, {
success: _.bind(function(model, response) {
// I should set id from JSON response when live
model.set({ id: this.collection.length });
this.insert(model);
}, this)
});
} else {
this.insert(item);
}
},
addAll: function() {
this.collection.each(this.addOne);
},
render: function() {
this.collection.deferred.done(_.bind(function() {
this.addAll();
}, this));
}
});
ns.AppView = Backbone.View.extend({
lists: [],
initialize: function(holder) {
holder.find("ul").each(_.bind(function(index, list) {
var Items = new WS.Content(list),
App = new WS.ListView(Items);
App.render();
this.lists.push(Items);
}, this));
}
});
})(WS);
$(document).ready(function() {
var App = new WS.AppView($("#tasks"));
});
You are on the right track. You will probably want to add the id of each sortable element into the template somewhere. Then when you receive the event, you know which model to add or remove from the collection. For example add...
<div data-id={{id}}> ... my thing ... </div>
And in the sortable call get the target's id attribute and call Collection.add() or remove()
Just use Backbone.CollectionView.. it has this functionality built in out of the box.
var listView = new Backbone.CollectionView( {
el : $( "#list1" ),
sortable : true,
sortableOptions : {
connectWith : "#list2"
},
collection : new Backbone.Collection
} );
var listView = new Backbone.CollectionView( {
el: $( "#list2" ),
sortable : true,
sortableOptions : {
connectWith : "#list1"
},
collection : new Backbone.Collection
} );

Resources