React State change is not Rendering - javascript

I'm fairly new to React and I'm having an issue where my initial state is rendering, but when the state is changed via AJAX call (successful) is not causing the function to render again. So what happens is the 'getInitialState' sets a static list of categories and 'componentDidMount' gets a new list from my API. The call executes correctly and triggers a success, so I don't know why the dropdown isn't updating.
var Dropdown = React.createClass({
getInitialState: function() {
return {
listVisible: false,
display: ""
};
},
select: function(item) {
this.props.selected = item;
},
show: function() {
this.setState({ listVisible: true });
document.addEventListener("click", this.hide);
},
hide: function() {
this.setState({ listVisible: false });
document.removeEventListener("click", this.hide);
},
render: function() {
return <div className={"dropdown-container" + (this.state.listVisible ? " show" : "")}>
<div className={"dropdown-display" + (this.state.listVisible ? " clicked": "")} onClick={this.show}>
<span>{this.props.selected.name}</span>
<i className="fa fa-angle-down"></i>
</div>
<div className="dropdown-list">
<div>
{this.renderListItems()}
</div>
</div>
</div>;
},
renderListItems: function() {
var categories = [];
for (var i = 0; i < this.props.list.length; i++) {
var category = this.props.list[i];
categories.push(<div onClick={this.select.bind(null, category)}>
<span>{category.name}</span>
<i className="fa fa-check"></i>
</div>);
}
return categories;
}
});
var GridFilter = React.createClass({
getInitialState: function() {
return {categoryList: [{
name: "Cat1",
value: "#F21B1B"
}, {
name: "Cat2",
value: "#1B66F2"
}, {
name: "Cat3",
value: "#07BA16"
}] };
},
getCategories: function() {
var nextPage = 1; //increase the page count
ajax_url = "http://127.0.0.1:8200/api/categories/";
ajax_type = "GET";
ajax_data = {};
$.ajax({
url: ajax_url,
type: ajax_type,
contentType: 'application/x-www-form-urlencoded',
data: ajax_data,
dataType: 'json',
success: function(data) {
this.setState({
data: this.state.categoryList,
});
//loading("off");
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
}, //end function
componentDidMount: function() {
this.getCategories();
},
render: function() {
return (
<div id="filter-bar" className="stamp">
<Dropdown list={this.state.categoryList} selected={this.state.categoryList[0]} />
<p className="filter-select">Categories <i className="fa fa-angle-down"></i></p>
<p className="filter-select">Type <i className="fa fa-angle-down"></i></p>
<p className="filter-text">Filters:</p>
</div>
);
}
});

if youre trying to change the categoryList state with your incoming data you need to change your set state to
this.setState({
categoryList: data,
});
what youre currently doing is adding a state with key data (even though there is no data key in your getInitialState function). and since youre not using this.state.data anywhere, nothing is changing, and so your ui does not appear to be updating

Related

Fetched data from ajax in component is not rendering in React

Following this. I have created a component and from that trying to fetch data using ajax request. I have written code for rendering it but it's not working and also no error after page load. I am new to react so not much idea, probably that's why unable to inspect the issue. Any help will be great.
// Js
React.createElement('tbody', {}, UserItem)
// Component
var UserItem = React.createClass({
propTypes: {
name: React.PropTypes.string.isRequired,
email: React.PropTypes.string,
dob: React.PropTypes.string,
},
getInitialState: function() {
return {
users: []
}
},
showResults: function(response) {
this.setState({
users: response
})
},
search: function(URL) {
$.ajax({
type: "GET",
dataType: 'json',
url: URL,
success: function(response){
this.showResults(response);
}.bind(this)
});
},
componentDidMount: function() {
this.search("users/usersData");
},
render: function() {
return (
UserItems
)
},
})
var UserItems = React.createClass({
render: function() {
var resultItems = this.state.users.map(function(result) {
return React.createElement(ResultItem, {id:result.id, name:result.name});
});
return React.createElement('div', {}, resultItems);
},
})
var ResultItem = React.createClass({
render: function(){
return React.createElement('tr', {},
React.createElement('td', {}, this.props.id),
React.createElement('td', {}, this.props.name),
React.createElement('td', {}, this.props.name),
React.createElement('td', {}, this.props.name)
)
}
});
You should implement render() method.
Something similar to this one should work:
render: function() {
return (
<div>
{this.state.users.map(function(user){
return <h2>{user}</h2>
})}
</div>
);
}
Try this
search: function(URL) {
var self = this;
$.ajax({
type: "GET",
dataType: 'json',
url: URL,
success: function(response){
self.showResults(response);
}.bind(this)
});
}

How to extend React.JS CommentBox example to allow deleting of comments?

I am trying to extend the React.JS CommentBox example to include the ability to delete comments. I have added a simple button to the Comment class and all of the necessary methods and listeners. Now I am trying to add functionality to a handleDelete method in my CommentBox class to do the necessary Ajax calls to delete the comment on server as well as locally
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
handleCommentSubmit: function(comment) {
var comments = this.state.data;
// Optimistically set an id on the new comment. It will be replaced by an
// id generated by the server. In a production application you would likely
// not use Date.now() for this and would have a more robust system in place.
comment.id = Date.now();
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
this.setState({data: comments});
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
loadCommentsFromServer: 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.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
handleDelete: function(id) {
var comments = this.state.data;
var newComments = [];
var commentToDelete = null;
for (var i = 0, len = comments.length; i < len; i++) {
if (comments[i].id != id) {
newComments.push(comments[i]);
} else {
commentToDelete = comments[i];
}
}
this.setState({data: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'DELETE',
data: commentToDelete,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
this.setState({data: comments});
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
<h1>Comments</h1>
<CommentList data={this.state.data} onCommentDelete={this.handleDelete} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
var CommentList = React.createClass({
handleDelete: function(id) {
console.log('handleDelete2 ' + id);
this.props.onCommentDelete(id);
},
render: function() {
var commentNodes = this.props.data.map(function(comment) {
var handle_delete = this.handleDelete.bind(this, comment.id);
return (
<Comment author={comment.author} key={comment.id} onCommentDelete={handle_delete}>
{comment.text}
</Comment>
);
}, this);
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
var CommentForm = React.createClass({
getInitialState: function() {
return {author: '', text: ''};
},
handleAuthorChange: function(e) {
this.setState({author: e.target.value});
},
handleTextChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
this.props.onCommentSubmit({author: author, text: text});
this.setState({author: '', text: ''});
},
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input
type="text"
placeholder="Your name"
value={this.state.author}
onChange={this.handleAuthorChange}
/>
<input
type="text"
placeholder="Say something..."
value={this.state.text}
onChange={this.handleTextChange}
/>
<input type="submit" value="Post" />
</form>
);
}
});
var Comment = React.createClass({
rawMarkup: function() {
var md = new Remarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
},
handleDelete: function(e) {
e.preventDefault();
console.log("handleDelete");
this.props.onCommentDelete();
},
render: function() {
var md = new Remarkable();
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={this.rawMarkup()} />
<button onClick={this.handleDelete}>Delete</button>
</div>
);
}
});
ReactDOM.render(
<CommentBox url="/api/comments" pollInterval={2000} />,
document.getElementById('content')
);
However, the item won't delete permanently and there is an error in the console:
Failed to load resource: the server responded with a status of 405 (Method Not Allowed)
/api/comments error Method Not Allowed
I really am not an Ajax expert so I am sure I am making a very obvious mistake.
Does anyone have any suggestions? Thanks in advance.

ReactJS for editing comments

I'm trying to figure out how to edit comments in ReactJS. I have been following this tutorial.
There are several theories I have on the solution:
Using mutable state instead of immutable props.
Has to do with the CommentBox component which has the loadCommentsFromServer and handleCommentSubmit functions. The loadComments function fires an AJAX request, possibly to my comments.json file.
Here's the relevant code from the server.js file
var COMMENTS_FILE = path.join(__dirname, 'comments.json');
app.get('/api/comments', function(req, res) {
fs.readFile(COMMENTS_FILE, function(err, data) {
if (err) { /* Print error to console */ }
res.json(JSON.parse(data));
});
});
// This snippet of code is probably the most important
app.post('/api/comments', function(req, res) {
fs.readFile(COMMENTS_FILE, function(err, data) {
if (err) { /* Print error to console */ }
var comments = JSON.parse(data);
var newComment = {
id: Date.now(),
text: req.body.text,
};
comments.push(newComment);
fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) {
if (err) { /* Print error to console */ }
res.json(comments);
});
});
});
Here's my main script file where I generate react components
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
// Trying to change the value of the text box on edit
<p onChange={this.handleTextChange()}> {this.props.text} </p>
</div>
);
}
});
var CommentBox = React.createClass({
When the component is first created, we need to get JSON data from the server and update the state with the latest data. this.setState() allows for dynamic updates. The old array of comments is being replaced by the new one
loadCommentsFromServer: 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)
});
},
This function passes data from the child components up to the parent components. It's supposed to submit to the server and refresh the list of comments
handleCommentSubmit: function(comment) {
var comments = this.state.data;
comment.id = Date.now();
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
this.setState({data: comments});
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(comment) {
return <Comment key={comment.id}>{comment.text}</Comment>
});
return <div className="commentList"> {commentNodes} </div>
);
}
});
Here a component is being created for filling out forms. this.state is used to save user's input as it's entered. I'm trying to accomplish this with the edit functionality.
var CommentForm = React.createClass({
getInitialState: function() {
return {text: ''};
},
handleTextChange: function(e) {
this.setState({text: e.target.value});
},
// This is also probably an important function
handleSubmit: function(e) {
e.preventDefault();
var text = this.state.text.trim();
this.props.onCommentSubmit({text: text});
this.setState({text: ''});
},
The value property of the input elements will reflect the state of the component and attach onChange handlers to them
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" value={this.state.text} onChange={this.handleTextChange} />
<input type="submit" value="Post" />
</form>
);
}
});
Finally, I am rendering the CommentBox component. The url attribute fetches dynamic data from the server. The pollInterval reloads the page every 2 seconds.
ReactDOM.render(
<CommentBox url="/api/comments" pollInterval={2000} />,
document.getElementById('content')
);
Here were my thoughts on how to implement the edit functionality
setTimeout(function() {
$('.edit').on('click', function() {
$(this).prev().prop('contentEditable', 'true');
$(this).prev().focus();
});
},1000);
I had to use setTimeout because it would take some time before the component's file to be loaded. I would then listen for clicking on the edit button, and change the html5 contentEditable property to true.
The problem I have is in updating the changes to the JSON file once it's edited.
I'd also like to know if there's a more react way of accomplishing this onclick functionality
As you can see in my component's file, I added an onChange handler to the paragraph that renders the body of the text.
render: function() {
return (
<div className="comment">
<p onChange={this.handleTextChange()}> {this.props.text} </p>
</div>
);
}
I've searched the internet extensively for examples of editing functionality but couldn't find anything.
My goal was to make this code as readable as possible. I tried to trim down code that is not immediately relevant to the problem at hand. I have removed the following code:
Declaration of npm variables and app.use
The listening of the server
The author fields for the text form. We only need the text field
It's usually not a very good idea to have jQuery mucking with React components (though jQuery + React can play nice with each other for certain tasks); we are running a large scale React application and have spent many hours removing instances of this from our early days.
In terms of saving the comments, you need a new endpoint to handle that functionality, it should look almost exactly like app.post('/api/comments') except instead of getting data from reading the file, it should get data from req.body, which is the data posted to it. To keep the same url this.props.url you could set it up as a PATCH endpoint: app.patch('/api/comments' ...). I'll leave that implementation up to you. The React save functionality should happen like this: the Comment component should use state to manage it's...state. Clicking "Edit" should switch that state to have the contentEditable set to true, "Edit" become "Save", etc. The actual saving part should be defined in the parent component CommentBox and should be passed down to the Comment component. Here is a basic idea of the changes you should make to allow editing, it's 100% untested but hopefully helps out some.
// changes to Comment component
var Comment = React.createClass({
getInitialState: function() {
return {
contentEditable: false,
buttonText: 'Edit',
text: this.props.text
};
},
handleButton: function() {
var commentTag = this.refs.comment;
// if the component is currently editable and the text is different from the original, save it
if (this.state.contentEditable && commentTag.textContent != this.state.text) {
this.props.onUpdateComment(this.props.id, commentTag.textContent);
}
// invert current contentEditable state Save => Edit or Edit => Save
var editable = !this.state.contentEditable;
this.setState({
contentEditable: editable,
// update the state to reflect the edited text
text: commentTag.textContent,
// change button text based on editable state
buttonText: editable ? 'Save' : 'Edit'
});
},
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">{this.props.author}</h2>
<p ref="comment" contentEditable={this.state.contentEditable}>{this.state.text}</p>
<button onClick={this.handleButton}>{this.state.buttonText}</button>
</div>
);
}
});
// changes to CommentList
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(comment) {
return <Comment onUpdateComment={this.props.onUpdateComment} {...comment} />
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
// changes to CommentBox
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.getJSON(this.props.url)
.then(function(newComments) {
this.setState({ data: newComments });
}.bind(this))
.fail(function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
});
},
handleCommentSubmit: function(comment) {
var comments = this.state.data;
comment.id = Date.now();
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.post(this.props.url, comments)
.then(function(data) {
this.setState({ data: data });
}.bind(this))
.fail(function(xhr, status, err) {
this.setState({ data: comments });
console.error(this.props.url, status, err.toString());
}.bind(this));
},
onUpdateComment: function(id, comment) {
// clone state, we don't want to alter this directly
var newData = this.state.data.slice(0);
newData.forEach(function(item) {
if(item.id === id) {
item.text = comment;
}
});
$.ajax({
url: this.props.url,
dataType: 'json',
method: 'PATCH',
data: newData
}).then(function(data) {
this.setState({ data: data });
}.bind(this));
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});

Delete a comment - React js

Actually, I've been trying to add a 'Delete a comment' functionality to my Comment Box system.
Here is my code:-
var Comment = React.createClass({
handleClick: function(e){
e.preventDefault();
var commentId = this.props.comment.id;
return this.props.onDelete(commentId);
},
render: function () {
return (
<div className="comment">
<h4 className="commentAuthor">
{this.props.comment.email}
</h4>
{this.props.comment.comment}
<a onClick={this.handleClick}> × </a>
</div>
);
}
});
var CommentList = React.createClass({
handleDelete: function(commentId){
return this.props.del(commentId);
},
render: function () {
var commentNodes = this.props.comments.map(function (comment, index) {
return (
<Comment comment = {comment} onDelete = {this.handleDelete} key = {index} />
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
var CommentBox = React.createClass({
getInitialState: function () {
return {comments: []};
},
componentDidMount: function () {
this.loadCommentsFromServer();
},
loadCommentsFromServer: function () {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function (comments) {
this.setState({comments: comments});
}.bind(this),
error: function (xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
handleCommentSubmit: function(comment) {
var comments = this.state.comments;
var newComments = comments.concat([comment]);
this.setState({comments: newComments});
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: {"comment": comment},
success: function(data) {
this.loadCommentsFromServer();
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
delc: function(commentId){
$.ajax({
url: this.props.url,
data: {"id" : commentId},
type: 'DELETE',
dataType: 'json',
success: function (comments) {
this.setState({comments: comments});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function () {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList del={this.delc} comments={this.state.comments} />
<CommentForm onCommentSubmit={this.handleCommentSubmit}/>
</div>
);
}
});
var CommentForm = React.createClass({
handleSubmit: function() {
var email = this.refs.email.getDOMNode().value.trim();
var comment = this.refs.comment.getDOMNode().value.trim();
this.props.onCommentSubmit({email: email, comment: comment});
this.refs.email.getDOMNode().value = '';
this.refs.comment.getDOMNode().value = '';
return false;
},
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="email" placeholder="Your email" ref="email" />
<input type="text" placeholder="Say something..." ref="comment" />
<input type="submit" value="Post" />
</form>
);
}
});
var ready = function () {
React.renderComponent(
<CommentBox url="18/comments.json" />,
document.getElementById('art')
);
};
$(document).ready(ready);
Now, when I try to delete a comment, it throws an error that says 'Uncaught TypeError: undefined is not a function'
Switching to 'source' tab on devTool, I found that the problem is in 'onDelete' function.
It says, 'onDelete' function is undefined.
I think the problem is due to 'this' keyword, but I'm not sure.
What should I do to resolve this issue? Let me know if I'm missing out on something.(I'm a newbie)
Thank you in advance.
The function passed to map will not automatically share a this pointer with your react class.
To use this inside the anonymous function, call .bind(this) at the end of the function definition to yield a function with the expected this inside.
var commentNodes = this.props.comments.map(function (comment, index) {
...
});
Becomes:
var commentNodes = this.props.comments.map(function (comment, index) {
...
}.bind(this));

KnockoutJS race condition during ajax call

Here is a weird race condition happening with knockoutjs. I'm setting two observables independantly using ajax calls. One is a list, the other is a single value. The weird thing is when I load the single value before the list, it won't bind correctly. Any suggestions?
JsFiddle: http://jsfiddle.net/JasonMore/bxfXd/110/
View
<form data-bind='submit:addItem'>
Add item: <input data-bind='value:itemToAdd, valueUpdate: "afterkeydown"' type='text' />
<button data-bind='enable: isAddButtonEnabled' type='submit'>Add</button>
</form>
<p>Your values:</p>
<select data-bind='options:allItems, value:selectedItems' height='5'> </select>
<div>
<button data-bind='click: removeSelected'>Remove</button>
<button data-bind='click: function() { allItems.sort() }, enable: allItems().length > 1'>Sort</button>
</div>
</div>
Code
var betterListModel = function() {
var self = this;
// properties
this.itemToAdd = new ko.observable("");
this.allItems = new ko.observableArray();
this.selectedItems = new ko.observable('');
// computed
this.isAddButtonEnabled = ko.computed(function() {
return self.itemToAdd().length > 0
});
//methods
this.addItem = function() {
if ((this.itemToAdd() != "") && (this.allItems.indexOf(this.itemToAdd()) < 0)) this.allItems.push(this.itemToAdd());
this.itemToAdd("");
}
this.removeSelected = function() {
this.allItems.removeAll(this.selectedItems());
this.selectedItems();
} };
var view = new betterListModel();
ko.applyBindings(view);
// load $.ajax({
url: '/echo/json/',
type: 'post',
data: {
json: $.toJSON("Ham"),
delay: 1
},
success: function(data) {
view.selectedItems(data);
} });
$.ajax({
url: '/echo/json/',
type: 'post',
data: {
json: $.toJSON(["Fries", "Eggs Benedict", "Ham", "Cheese"]),
delay: 2
},
success: function(data) {
$.each(data, function(index, value) {
view.allItems.push(value);
});
} });
Try this-->
// Whenever the states changes, reset the selectedState selection
this.allItems.subscribe(function () {
this.selectedItems(arrayOfMySelectedItems);
});

Categories