As part of my ongoing effort to learn ReactJS I'm developing a simple page that will render a list of trends as follows:
On clicking the button "Get Trends" the list of trends is retrieved from a back-end server over websockets and displayed. It works as expected. Below is the corresponding source code:
import React, { Component } from 'react';
import './App.css';
class TrendRow extends Component {
render() {
return (
<tr>
<td>{this.props.onerow}</td>
</tr>
);
}
}
class TrendTable extends Component {
render() {
var rows = [];
for (var i=1; i<3; i++) {
rows.push(<TrendRow key={i} onerow={this.props.trends[i]}/>);
}
return (
<table>
<thead>
<tr>
<th>List of Trends</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class App extends Component {
constructor() {
super();
this.state = {
ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService'),
getTrendsqueryString: 'GETTRENDS',
listoftrends: ['{"trend":"#Default","id":"0"}']
};
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick = (event) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
this.setState(prevState => ({listoftrends: prevState.listoftrends.concat(event.data)}));
}
}
render() {
return (
<div>
<button onClick={this.handleOnClick}>Get Trends</button>
<TrendTable trends={this.state.listoftrends} />
</div>
);
}
}
export default App;
Now when I try to convert the JSON string displayed into a JSON object using "JSON.parse" I get different types of errors based on where I parse it.
If I parse as shown below,
class TrendRow extends Component {
render() {
var jsonobject = JSON.parse(this.props.onerow);
return (
<tr>
<td>{jsonobject}</td>
</tr>
);
}
}
I get the below error:
"SyntaxError: Unexpected token u in JSON at position 0
...
var jsonobject = JSON.parse(this.props.onerow);
..."
A quick google search for the error message returned the following discussion but it's not very clear how to apply the solution to my use-case:
uncaught syntaxerror unexpected token U JSON
I understand that the error is due to value 'this.props.onerow' being undefined during the initial render and JSON.parse() trying to parse this object. Even initializing the "listoftrends" object with a default string does not solve the error.
If on the other hand I JSON.parse() as shown below,
handleOnClick = (event) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
var jsonobject = JSON.parse(event.data);
this.setState(prevState => ({listoftrends: prevState.listoftrends.concat(jsonobject)}));
}
}
I get the error message:
Objects are not valid as a React child...
Google search leads me down another rabbit hole! Could someone please provide any other solutions for me to try?
As part of my ongoing effort to learn ReactJS I'm developing a simple page that will render a list of trends as follows:
On clicking the button "Get Trends" the list of trends is retrieved from a back-end server over websockets and displayed. It works as expected. Below is the corresponding source code:
import React, { Component } from 'react';
import './App.css';
class TrendRow extends Component {
render() {
return (
<tr>
<td>{this.props.onerow}</td>
</tr>
);
}
}
class TrendTable extends Component {
render() {
var rows = [];
for (var i=1; i<3; i++) {
rows.push(<TrendRow key={i} onerow={this.props.trends[i]}/>);
}
return (
<table>
<thead>
<tr>
<th>List of Trends</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class App extends Component {
constructor() {
super();
this.state = {
ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService'),
getTrendsqueryString: 'GETTRENDS',
listoftrends: ['{"trend":"#Default","id":"0"}']
};
this.handleOnClick = this.handleOnClick.bind(this);
}
handleOnClick = (event) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
this.setState(prevState => ({listoftrends: prevState.listoftrends.concat(event.data)}));
}
}
render() {
return (
<div>
<button onClick={this.handleOnClick}>Get Trends</button>
<TrendTable trends={this.state.listoftrends} />
</div>
);
}
}
export default App;
Now when I try to convert the JSON string displayed into a JSON object using "JSON.parse" I get different types of errors based on where I parse it.
If I parse as shown below,
class TrendRow extends Component {
render() {
var jsonobject = JSON.parse(this.props.onerow);
return (
<tr>
<td>{jsonobject}</td>
</tr>
);
}
}
I get the below error:
"SyntaxError: Unexpected token u in JSON at position 0
...
var jsonobject = JSON.parse(this.props.onerow);
..."
A quick google search for the error message returned the following discussion but it's not very clear how to apply the solution to my use-case:
uncaught syntaxerror unexpected token U JSON
I understand that the error is due to value 'this.props.onerow' being undefined during the initial render and JSON.parse() trying to parse this object. Even initializing the "listoftrends" object with a default string does not solve the error.
If on the other hand I JSON.parse() as shown below,
handleOnClick = (event) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
var jsonobject = JSON.parse(event.data);
this.setState(prevState => ({listoftrends: prevState.listoftrends.concat(jsonobject)}));
}
}
I get the error message:
Objects are not valid as a React child...
Google search leads me down another rabbit hole! Could someone please provide any other solutions for me to try?
Share Improve this question edited Jun 20, 2020 at 9:12 CommunityBot 11 silver badge asked Sep 8, 2017 at 10:58 Hari NHari N 1674 silver badges11 bronze badges5 Answers
Reset to default 3Looking at your code (and your note that you are just learning) let me add few ments how you can possibly improve it.
Using an arrow function with a class property ensures that the method is always invoked with the ponent as the value for
this
, meaning that the manual binding here is redundant. So you can get rid of the line below.this.handleOnClick = this.handleOnClick.bind(this);
Also get rid of the ugly
for
loop inside theTrendTable
and replace it with map function.class TrendTable extends Component { render() { return ( <table> <tbody>{this.props.trends.map(trend => <TrendRow key={i} onerow={trend}/>)} </tbody> </table> ); } }
You can read more about what alternatives you have if you are going to avoid regular
for
loop.For prepopulating your
trends
array the better approach is to useponentDidMount
react lifecycle method. To dive deeper check this article.And I think it's better to create update button (instead of get trends) which should pletely rewrite your trends list with fresh portion from backend side in case you need to (but sure this point is up to you).
Now you don't have to use constructor method inside ponent if you just only need to initialize default state, so you can use
state = {....};
just inside your ponent w/o usage of constructor. But make sure that you are using stage-2 preset.
So taking into account the ments above, here is the App ponent:
class App extends Component {
state = {
ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService'),
getTrendsqueryString: 'GETTRENDS',
listoftrends: [] // you can add default trend record if you need it
};
};
ponentDidMount() {
this.fetchTrends();
}
fetchTrends = (pleteUpdate = false) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
this.setState(prevState => (
{ listoftrends: !pleteUpdate ? prevState.listoftrends.concat(event.data) : event.data }
));
}
};
updateTrends = () => {
this.fetchTrends(true); //in that case you'd like to pletely update the list
}
}
render() {
return (
<div>
<button onClick={this.updateTrends}>Update trends</button>
<TrendTable trends={this.state.listoftrends} />
</div>
);
}
}
- And regarding your question itself. As many of other guys already mentioned, yes it's not possible to use an object inside of JSX, so you have to transform it first (for instance to array).
E.g.
var array = Object.values(jsonobject).map(value => ...);
// and then in JSX
<div>{array}</div>
Hopefully it all makes sense.
jsonobject
is an object.
React don't know how to render it.
You can convert it to an Array and then render it it.
var jsonArray = Object.keys(jsonobject).map(function(k) {
return jsonobject[k];
});
JSX :
<td>{jsonobject}</td>
A simple solution to make your first approach work would be to circumvent the Error by not attempting to parse JSON during the first render:
var jsonobject = this.props.onerow ? JSON.parse(this.props.onerow) : {};
As others have said though, you cannot render objects directly. For testing purposes, replace <td>{jsonobject}</td>
with <td>{jsonobject.id}</td>
.
If you log the initial row, AND results of your JSON parsing, e.g.
console.log(this.props.onerow);
console.log(JSON.parse(this.props.onerow));
What is the console's output? Your first error is probably because your 'JSON' response from the backend isn't properly structured, or because initially if the value is something like an empty string then you can't use JSON.parse on it as it will cause an error.
Your second error, Objects are not valid as a React child
, is what happens when you try to render an object inside a JSX element, for example:
render(){
let someObj = {potato:1, hello:'hi there'};
return(<div>Here's the object: {someObj}</div>); <-- ERROR! can't put an object here
}
So I'm guessing at some point you're trying to render that JSON object inside a JSX element.
I have provided the updated code after incorporating the suggestions provided by users. This may not be perfect but acplishes what I wanted.
import React, { Component } from 'react';
import './App.css';
class TrendRow extends Component {
render() {
var jsonArray = Object.keys(this.props.onerow).map((k) => {
return this.props.onerow[k];
});
return (
<tr>
<td>{jsonArray[0]}</td>
</tr>
);
}
}
class TrendTable extends Component {
render() {
var rows = this.props.trends.map((trend, index) => {
return (index<2) ? <TrendRow key={index} onerow={trend} /> : [];
}
);
return (
<table>
<thead>
<tr>
<th>List of Trends</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}
}
class App extends Component {
constructor() {
super();
this.state = {
ws: {},
getTrendsqueryString: 'GET',
listoftrends: []
};
}
ponentDidMount = () => {
this.setState({ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService')});
}
handleOnClick = (event) => {
this.state.ws.send(this.state.getTrendsqueryString);
this.state.ws.onmessage = (event) => {
var jsonobject = JSON.parse(event.data);
this.setState(prevState => ({listoftrends: prevState.listoftrends.concat(jsonobject)}));
}
this.state.ws.onclose = (event) => {
this.setState({ws: new WebSocket('ws://localhost:8025/websockets/TwitterWSService')});
}
}
render() {
return (
<div>
<button onClick={this.handleOnClick}>Get Trends</button>
<TrendTable trends={this.state.listoftrends} />
</div>
);
}
}
export default App;