ReactJS: How to best handle JSON response of newly created object - javascript

I have three React components: the first is a container (NoteContainer component) which is responsible for rendering my objects (Note component) in the UI. The data is obtained as JSON via AJAX GET. The last component is a form (NoteForm) which creates new objects (via AJAX POST).
The response from the POST is only the JSON representation of the newly created object, not the JSON for all of the objects.
Should the NoteForm send the JSON response from creating a new object to the NoteContainer which would append it to its state.data and re-render , or should the NoteContainer request the full list of objects and update its state date entirely?
I would presume the first way is better since it does not require requesting data which is already present in the state of NoteContainer. However, I'm still not sure of the "best" way to handle this. Should I give NoteContainer another function, something like addNewNote, which would take the JSON data from the NoteForm and append it to state.data?
I'm new to React so I apologize if this is not a clear question. Here are my components:
var NoteContainer = React.createClass({
getInitialState: function(){
return {data: []};
},
componentDidMount: 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)
});
},
render: function(){
var noteNodes = this.state.data.map(function(note){
return (
<Note title={note.title} body={note.body} />
);
});
return (<div className='noteContainer'>{noteNodes}</div>);
}
});
var Note = React.createClass({
render: function(){
return (
<div className="note" >
<h1>{this.props.title}</h1>
<p>{this.props.body}</p>
</div>
);
}
});
var NoteForm = React.createClass({
getInitialState: function(){
return {'title': '', 'body': ''}
},
handleTitleChange: function(e){
this.setState({title: e.target.value});
},
handleBodyChange: function(e){
this.setState({body: e.target.value});
},
handleSubmit: function(e){
e.preventDefault();
var note = {
title: this.state.title,
body: this.state.body};
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: note,
success: function(data){
// Send data to NoteContainer?
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function(){
return (
<form>
<input
type='text'
placeholder='Title'
value={this.state.title}
onChange={this.handleTitleChange} />
<textarea onChange={this.handleBodyChange}>
{this.state.body}
</textarea>
</form>
);
}
});

What #xCrZx is suggesting is that you pull the state outside of the individual components and have a one or more top-level stores to maintain state. The simplest (i.e. "vanilla") example of this would be if your NoteContainer was a parent of NoteForm. Then you could simply pass a callback from NoteContainer to NoteForm:
var NoteContainer = React.createClass({
createNote: function() {
...
},
render: function() {
return (
...
<NoteForm createNote={this.createNote}>
...
);
}
});
var NoteForm = React.createClass({
props: {
createNote: React.PropTypes.func.isRequired
},
render: function() {
return (
...
onClick={this.props.createNote}
...
);
}
});
However, that of course only works if the relationship actually exists. Now let's take a look at Reflux, where you create central stores (and actions to go with them) to keep data, and components "listen" to the stores.
var NoteActions = Reflux.createActins([
'createNote',
'getNotes'
]);
var NoteStore = Reflux.createStore({
listenables: [NoteActions],
init: {
// Notes is an empty array by default
this.notes = [];
},
getInitialState: function() {
return {
notes: this.notes
};
},
onCreateNote: function(noteFormData) { ...POST here, save note JSON to this.notes on success... },
onGetNotes: function() { ..GET here for the initial load...}
});
var NoteForm = React.createClass({
render: function() {
...
onClick={NoteActions.createNote(noteFormData)}
...
}
});
var NoteContainer = React.createClass({
mixins: [Reflux.connect(NoteStore)],
componentDidMount: function() {
NoteActions.getNotes();
},
render: function() {
return: function() {
.. same that you have now using this.state.notes
}
}
});
Hope this is starting to make sense. Highly recommend looking through the Reflux (or Redux, similar but different) examples.

The best approach is to keep all notes in global state and add new entities there one by one when needed. It can be achieved with help of global stores, like Redux or Reflux.

Related

ReactJs render simple JSON

I have simple json
{"id":157,"content":"Hello, World!"}
I want to render id in one div and content in anotther. Problem for me is when I call {this.state.data.content} twice it crash.
var Stuff = React.createClass({
getInitialState: function () {
return {
data: []
};
},
componentDidMount: function() {
$.ajax({
url: "http://rest-service.guides.spring.io/greeting",
dataType: 'json',
cache: false,
success: function(response) {
this.setState({
data: response
});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function() {
return (
<div>Response - {this.state.data.content}</div>
);
}
});
The problem solved by surrounding jsx with another div
return (
<div>
<div>Response - {this.state.data.content}</div>
<div>id - {this.state.data.id}</div>
</div>
);
Well, try to render a string instead of an object
render: function() {
return (<div> Whatever - {JSON.stringify(this.state.data)}</div>)
}
Tipp:
if you want to make it pretty: use JSON.stringify(this.state.data, null, 2)

ReactJS Update ajax call to child component from onClick method from parent

I have an array of data being passed from one json file.
When I click on the list item it triggers a handleClick method that will get the new url and set it to the provided state. That state is then passed onto a child component that makes an ajax call using that link.
The problem is that the ajax call only loads once when the component is mounted and does not make anymore calls after that no matter how many times I click on it.
How would I get the child component to load a new url each time a different item is clicked?
Parent Component:
getInitialState: function() {
return {
gameUrl: ''
};
},
handleClick: function() {
this.setState({
gameUrl: gameUrl
});
},
render: function() {
var gameList = this.props.data.map(function(game) {
var homeTeamName = game.home_team_name;
var awayTeamName = game.away_team_name;
var gameUrl = game.game_directory+'/anotherFile.json';
console.log(homeTeamName+' vs '+awayTeamName+':'+ gameUrl);
var click = this.handleClick;
return (
<li key={game.location} className="list-group-item" onClick={click}>
{homeTeamName}
</li>
);
}, bind);
return (
<div>
<ul>
{gameList}
</ul>
<GameDetail url={this.state.gameUrl}/>
</div>
);
Child Component:
componentDidMount: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({
data: data.data
});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
Implement componentWillReceiveProps method.
It will be called when the props have changed and when this is not an initial rendering.Then update the state depending on the existing and upcoming props.
componentWillReceiveProps: function(nextProps) {
this.setState({
// set something
});
}
Thanks to #WitVault managed to do it here is the revised section:
Instead of componentDidMount, I changed it to componentWillReceiveProps in the child component. Make sure the prop you're passing through is the same in the child and parent component. (i.e. in child component the prop is url, in the parent you're passing it to the same prop <GameDetail url={this.state.gameUrl}/>) Then you access it in the componentWillReceiveProps method via nextProps.url
componentWillReceiveProps: function(nextProps) {
// Access the url prop
var newUrl = nextProps.url;
$.ajax({
url: newUrl,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({
data: data.data
});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},

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>
);
}
});

React app with Parent calling AJAX request and then child component calling another ajax request

So say I have a React parent component that does an AJAX request on the componentDidMount that sets state and then in the render I have a child component that will be taking some of that data from the state and will be doing another AJAX request inside the child component with that information, how do I go about this?
For instance:
var Parent = React.createClass({
getInitialState: function() {
return { num: '' };
},
componentDidMount: function() {
$.ajax({
url: '/urltogetdata',
method: 'GET',
dataType: 'json',
success: function(data) {
if (this.isMounted()) {
this.setState({num: data['num']});
}
}.bind(this)
});
},
render: function() {
return (
<div>
<Child num={this.state.num}/>
</div>
);
},
});
var Child = React.createClass({
getInitialState: function() {
return { childInfo: {} };
},
componentDidMount: function() {
$.ajax({
url: '/urltogetdata/' + this.props.num,
method: 'GET',
dataType: 'json',
success: function(data) {
if (this.isMounted()) {
this.setState({childInfo: data});
}
}.bind(this)
});
},
renderChildChild: function() {
return (<div><ChildChild /></div>);
},
render: function() {
return (
<div>
{this.state.childInfo.map(this.renderChildChild)}
</div>
);
},
});
Now the problem I'm getting is that all these renders are happening before I get information. I can usually get the data and display it on the Parent side, but when it gets back down to the Child with the Parent's state (that is set from the AJAX request), I lose a render when I try to setState, because it is already set.
I've tried it with shouldComponentUpdate, componentWillUpdate and various other things and I'm at a loss. I don't really know what else to try or if it's just something I have to deal with because AJAX and asynchronous rendering in React just isn't there yet?

State not passed into prop

I'm using React with Rails 4.
I've got the following:
<%= react_component('Box',url: "blah", pollInterval: 2000) %>
and then my components:
var Box = React.createClass({
getInitialState: function () {
return {data: []};
},
loadStuffFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
console.log(data);
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.loadStuffFromServer();
setInterval(this.loadStuffFromServer, this.props.pollInterval);
},
render: function() {
return (
<div>
<Widget office="1" data="{this.state.data}"/>
</div>
);
}
});
var Widget = React.createClass({
render: function () {
return (
<div>
{this.props.data}
</div>
)
};
});
For the Box, I can see using React DevTools for Chrome that the state is being set to the JSON returned by the url. However for the Widget component, when I try to echo the props out it literally returns: {this.state.data}
So the props is being set, but to a string instead of the JSON array?
Any property inside quotes is a string:
<Widget office="1" data="{this.state.data}"/>
To use a JavaScript expression, use only the curlies:
<Widget office="1" data={this.state.data}/>

Categories