ReactJs render simple JSON - javascript

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)

Related

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

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.

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}/>

React State Change Not Causing component to Re-Render

I'm still new to ReactJS, and I've run into a bit of a snag. I'm trying to implement pagination in a React component; however, even though my pagination function is being called successfully the state change is not causing the component to Render again.
I'm using one function to get the initial data (getMainFeed) and then a second one (getMoreItems) for the pagination. The second function is called by my 'handleScroll' function. Interestingly enough, when I replace the 'getMoreItems' function with the 'getMainFeed' function, the state change causes the component to render the additional data perfectly. Unfortunately I need to hit these two separate APIs and I don't think it would be in good form to combine the two calls into one function. So is there a way that I can get 'getMoreItems' to render the new items to the screen?
var data = [];
var GridView = React.createClass({
getInitialState: function() {
window.addEventListener("scroll", this.handleScroll);
return {
data: [],
page: 0, //for pagination
loadingFlag: false,
};
},
getMainFeed: function() {
var nextPage = 1; //increase the page count
ajax_url = "http://127.0.0.1:8200/api/content/main/";
ajax_type = "GET";
ajax_data = {
'BatchCount': "20"
};
$.ajax({
url: ajax_url,
type: ajax_type,
contentType: 'application/x-www-form-urlencoded',
data: ajax_data,
dataType: 'json',
success: function(data) {
this.setState({
data: data,
loadingFlag:false,
page: 2
});
//loading("off");
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
}, //end function
getMoreItems: function() {
var nextPage = this.state.page+1; //increase the page count
ajax_url = "http://127.0.0.1:8200/api/content/page/1/";
ajax_type = "GET";
ajax_data = {
'BatchCount': "20"
};
$.ajax({
url: ajax_url,
type: ajax_type,
contentType: 'application/x-www-form-urlencoded',
data: ajax_data,
dataType: 'json',
success: function(data) {
this.setState({
data: data,
loadingFlag:false,
page: nextPage
});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
}, //end function
componentDidMount: function() {
//loading("on");
this.getMainFeed();
},
handleScroll:function(e){
//this function will be triggered if user scrolls
var windowHeight = $(window).height();
var inHeight = window.innerHeight;
var scrollT = $(window).scrollTop();
var totalScrolled = scrollT+inHeight;
if(totalScrolled+1200>windowHeight){ //user reached at bottom
if(!this.state.loadingFlag){ //to avoid multiple request
this.setState({
loadingFlag:true,
});
//loading("on");
this.getMoreItems();
}
}
},
componentDidUpdate: function() {
$('#grid-container').imagesLoaded( function() {
MasonryInit();
});
},
render: function() {
return (
<div id="feed-container-inner">
<GridMain data={this.state.data} />
</div>
);
}
});
When the state change it will re-render. Also you saw that it was working with your getmainfeed function.
So I think that your getmoreitems function just do not success. Have you verified that this call succeed ?
The problems turns out to be that I wasn't concatenating the data to the end of the existing data like so data: this.state.data.concat(data),.
This surprises me, because I would have expected the getMoreItems function to simply replace the existing data.

Reactjs this.state giving Uncaught TypeError: Cannot read property 'groupsData' of null

I am doing 2 basic ajax calls to 2 different apis in one of my ReactJs components. Although, when running the call (on urls I know for certain are working and returning data), I receive:
Uncaught TypeError: Cannot read property 'groupsData' of null
Here is the single component:
var BrowseWidgetBox = React.createClass({
getGroupsApi: function(){
$.ajax({
url: this.props.groupsApi,
dataType: 'json',
type: 'GET',
success: function(groupsData){
this.setState({groupsData: groupsData});
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.groupsApi ,status, err.toString());
}.bind(this)
});
},
getItemsApi: function() {
$.ajax({
url: this.props.itemsApi,
dataType: 'json',
type: 'GET',
success: function(itemsData){
this.setState({itemsData: itemsData});
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.groupsApi ,status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.getGroupsApi();
this.getItemsApi();
},
render: function() {
return (<div className="BrowseWidgetBox">
<MainMenu groupsData={this.state.groupsData} itemsData={this.state.itemsData} />
<Display />
</div>);
}
});
React.render(
<BrowseWidgetBox groupsApi="/*imagine a working url here*/" itemsApi="/*imagine a working url here*/" />, document.getElementById('widget-container')
);
Is there something obvious I am missing in terms of reactJS/ ajax?
More actual answer, dependent from used standart:
ES6 Classes
export class Component extends React.Component {
constructor(props) {
super(props);
this.state = { groupsData: {}, itemsData: {} };
}
...
}
ES7+ Classes
export class Counter extends React.Component {
state = { groupsData: {}, itemsData: {} };
...
}
You should add getInitialState method to your component, where you should set initial state
var BrowseWidgetBox = React.createClass({
getInitialState: function () {
return {groupsData: {}, itemsData: {}};
},
// your code
});

reactjs Uncaught TypeError: Cannot read property 'map' of undefined

I have used getInitialState on my BrowseWidgetBox component. Although when passing the data to my MainMenu component, the data remains empty, as if the AJAX call to the api was never run in the BrowseWidgetBox.
My question then, is why is this happening? Shouldn't componentDidMount call the ajax api and re-set the state to include the contents of the ajax call? I want the state of my groupsData and my itemData to be present when the page is initially loaded. I am a bit worried that getInitialState is hindering the calls to ajax at least 'initially' which is causing my error.
Here is the full code of the two components:
var MainMenu = React.createClass({
render: function() {
console.log(this.props.groupsData); // console.log here
var categories = this.props.groupsData.objects.map(function(obj){
return (<li>obj.description</li>);
});
return (<div className="MainMenu">
<ul>{categories}</ul>
</div>);
}
});
var BrowseWidgetBox = React.createClass({
getInitialState: function () {
return {groupsData: {}, itemsData: {}};
},
getGroupsApi: function(){
$.ajax({
url: this.props.groupsApi,
dataType: 'json',
type: 'GET',
success: function(groupsData){
this.setState({groupsData: groupsData});
console.log(groupsData) // Console.log here
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.groupsApi ,status, err.toString());
}.bind(this)
});
},
getItemsApi: function() {
$.ajax({
url: this.props.itemsApi,
dataType: 'json',
type: 'GET',
success: function(itemsData){
this.setState({itemsData: itemsData});
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.groupsApi ,status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.getGroupsApi();
this.getItemsApi();
},
render: function() {
return (<div className="BrowseWidgetBox">
<MainMenu groupsData={this.state.groupsData} itemsData={this.state.itemsData} />
<Display />
</div>);
}
});
React.render(
<BrowseWidgetBox groupsApi="http://this/is/a/good/url" itemsApi="http://this/is/a/good/api/call" />, document.getElementById('widget-container')
);
You're trying to use the map in object...
In
getInitialState: function () {
return {groupsData: {}, itemsData: { objects: [] }};
},
the first render are getting a object in groupsData
try change to
var MainMenu = React.createClass({
render: function() {
console.log(this.props.groupsData); // console.log here
var categories = this.props.groupsData.objects.map(function(obj){
return (<li>obj.description</li>);
});
return (<div className="MainMenu">
<ul>{categories}</ul>
</div>);
}
});
var BrowseWidgetBox = React.createClass({
getInitialState: function () {
return {groupsData: { objects: [] }, itemsData: []};
},
getGroupsApi: function(){
$.ajax({
url: this.props.groupsApi,
dataType: 'json',
type: 'GET',
success: function(groupsData){
this.setState({groupsData: groupsData});
console.log(groupsData) // Console.log here
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.groupsApi ,status, err.toString());
}.bind(this)
});
},
getItemsApi: function() {
$.ajax({
url: this.props.itemsApi,
dataType: 'json',
type: 'GET',
success: function(itemsData){
this.setState({itemsData: itemsData});
}.bind(this),
error: function(xhr, status, err){
console.error(this.props.groupsApi ,status, err.toString());
}.bind(this)
});
},
componentDidMount: function() {
this.getGroupsApi();
this.getItemsApi();
},
render: function() {
return (<div className="BrowseWidgetBox">
<MainMenu groupsData={this.state.groupsData} itemsData={this.state.itemsData} />
<Display />
</div>);
}
});
React.render(
<BrowseWidgetBox groupsApi="http://this/is/a/good/url" itemsApi="http://this/is/a/good/api/call" />, document.getElementById('widget-container')
);

Categories