Render an object from an array of objects in React - javascript

I have the problem that this.state.languages which is an array of objects renders in react as [{"ID":1,"NAME":"Deutsch","SHORT_NAME":"de"},{"ID":2,"NAME":"English","SHORT_NAME":"en"}], but when I try to access the first element with this.state.languages[1] it just renders as {. When I try to use this.state.languages.map(...) I get the following error: TypeError: this.state.languages.map is not a function.
this.state.languages is an array containing objects with the properties ID, NAME, SHORT_NAME
I try to render the array and the properties by using:
export default class App extends Component{
constructor(props){
super(props);
this.state = {
languages: []
};
}
componentDidMount(){
fetch("http://localhost:8080/data/sprachen")
.then(response => response.json())
.then(responseJson => {
this.setState({ languages: responseJson.data });
})
}
render(){
return(
<div className="container">
<p>{this.state.languages}</p>
{this.state.languages.map(function(i, item){
return <li key={i}>{item}</li>
})}
</div>
)
}
}
This is my responseJson: {"data": [{"ID":1,"NAME":"Deutsch","SHORT_NAME":"de"},{"ID":2,"NAME":"English","SHORT_NAME":"en"}]}

response.json() should give you the JSON object directly (it shouldn't be in a data sub-key). So the correct code should be:
.then(responseJson => {
this.setState({ languages: responseJson });
})

The map function takes current value as first argument and index as second argument
Also it is not advisable to use the index as key while rendering children as it affects performance. I did not understand your exact requirements. I have included some code for reference
render() {
return(
<div className="container">
<ul>
{this.state.languages.map((item, index) => <li key={item.id}>{item.NAME}</li>)}
</ul>
</div>
)
}

Related

Map is not a function : {} JSON array

I just went from Vue to React and I'm a little lost on the iteration of an array.
With the same API, everything works with Vue but not with React.
Here is an example of an answer from my API:
{
"blade": {
"id":"1",
"key":"blade"
},
"sword": {
"id":"2",
"key":"sword"
}
}
I think the problem is that my API response returns an array but with the symbols {} and not []
If the problem comes from this, how can I solve it?
This is my current code:
class ItemSelection extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [],
};
}
componentDidMount() {
fetch('https://myapi.com/items.json')
.then(response => response.json())
.then(data => this.setState({ items: data }));
}
render() {
return (
<div>
{
this.state.items.map(item => (
<div>...</div>
))
}
</div>
);
}
}
The response is a Object.... you can use the function Object.values(items) to get a list of values and use the map function.
.then(data => this.setState({ items: Object.values(data) }));
I solved a similar problem with data = Array.from(data);
{} denotes a JSON object, not an array, a map object is also treated in a similar way. Therefore, you can access the elements of the object/map using as below
<div v-for="item in items">
<p><strong>Id:</strong> {{ item.id}}</p>
<p><strong>Key:</strong>{{ item.key}}</p>
</div>
for more information, you can refer https://v2.vuejs.org/v2/guide/list.html

Fetch one JSON object and use it reactjs

I'm very new to JS and ReactJS and I try to fetch an endpoint which gives me a JSON Object like this :
{"IDPRODUCT":4317892,"DESCRIPTION":"Some product of the store"}
I get this JSON Object by this endpoint :
http://localhost:3000/product/4317892
But I dont how to use it in my react application, I want to use those datas to display them on the page
My current code looks like this but it's not working and I'm sure not good too :
import React, {Component} from 'react';
class Products extends Component {
constructor(props){
super(props);
this.state = {
posts: {}
};
};
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
const { postItems } = this.state;
return (
<div>
{postItems}
</div>
);
}
}
export default Products;
In the console.log(this.state) there is the data, but I'm so confused right now, dont know what to do
Since I'm here, I have one more question, I want to have an input in my App.js where the user will be able to type the product's id and get one, how can I manage to do that ? Passing the data from App.js to Products.js which is going to get the data and display them
Thank you all in advance
Your state doesn't have a postItems property which is considered undefined and react therefore would not render. In your situation there is no need to define a new const and use the state directly.
Also, when you setState(), you need to tell it which state property it should set the value to.
componentWillMount() {
fetch('http://localhost:3000/product/4317892')
.then(res => res.json())
.then(res => {
this.setState({
...this.state, // Not required but just a heads up on using mutation
posts: res
})
})
.catch((error => {
console.error(error);
}));
}
render() {
console.log(this.state)
return (
<div>
<p><strong>Id: {this.state.posts.IDPRODUCT}</strong></p>
<p>Description: {this.state.posts.DESCRIPTION}</p>
</div>
);
}
I have got 3 names for the same thing in your js: posts, postItems and res.
React can not determine for you that posts = postItems = res.
So make changes like this:
-
this.state = {
postItems: {}
};
-
this.setState({
postItems: res
});
-
return (
<div>
{JSON.stringify(postItems)}
<div>
<span>{postItems.IDPRODUCT}</span>
<span>{postItems.DESCRIPTION}</span>
</div>
</div>
);
{postItems["IDPRODUCT"]}
Will display the first value. You can do the same for the other value. Alternatively, you can put
{JSON.stringify(postItems)}
With respect to taking input in the App to use in this component, you can pass that input down through the props and access it in this component via this.props.myInput. In your app it'll look like this:
<Products myInput={someInput} />

Even after passing updated value from parent to child, child is not rendered

In the parent component, I receive data from the server and then map this data into a jsx format. Inside this mapping I have a child component and try to pass a value from state of parent to child as a property, however when I update state of this value, the render function for child is not executed.
Expected behavior: As a user I see a list of items. If I click on an item it should become as checked.
export class ReactSample extends React.Component {
constructor(props){
super(props);
this.state = {
items: [],
mappedItems: [],
selectedIds: [],
isSelected: false,
clickedTripId: null
};
this.toggleSelection = this.toggleSelection.bind(this);
}
componentWillMount(){
console.log("Component mounting")
}
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({selectedIds:
state.selectedIds.concat(id)}));
this.setState(() => ({clickedTripId: id}));
this.mapItems(this.state.items);
}
}
componentDidMount() {
const self = this;
MyService.getItems()
.then(res => {
self.setState(() => ({ items: res.allItems }));
self.setState(() => ({ mappedItems:
this.mapItems(res.allItems) }));
}
)
}
mapItems (items) {
return items.map(trip => {
return (
<li key={trip.id} onClick={(e) => (this.toggleSelection(trip.id,
e))}>
<span>{trip.title}</span>
<Tick ticked={this.state.clickedTripId}/>
<span className="close-item"></span>
</li>
);
});
}
getItems() {
}
render() {
return (
<div>
<a className="title">This is a react component!</a>
<Spinner showSpinner={this.state.items.length <= 0}/>
<div className="items-container">
<ul id="itemsList">
{this.state.mappedItems}
</ul>
</div>
</div>
);
}
}
export class Tick extends React.Component {
constructor(props) {
super(props);
}
render() {
console.log('RENDER');
return (<span className={this.props.ticked ? 'tick display' :
'tick hide' }></span>);
}
}
I see a couple issues.
In toggleSelection you aren't doing anything with the result of mapItems. This kind of bug would be much easier to avoid if you just remove mappedItems from state and instead just call mapItems within your render method.
The other issue is you are passing this.state.clickedTripId as the ticked property. I assume you meant to pass something more like this.state.clickedTripId === trip.id.
As Ryan already said, the problem was that mappedItems where not updated when toggleSelection was clicked. As it is obvious from the code mapItems returns data in jsx format. To update it I had to call this.setState({mappedItems: this.mapItems(this.state.items)}) which means that I call mapItems and then I assign the result to the state. In this case my list will be updated and Tick component will receive this.state.clickedItemId as a tick property. There is one more issue that needs to be done to make this code working:
this mapped list needs to be updated after this.state.clickedItemId is updated. The method setState is asynchronous which means that this.setState({mappedItems: this.mapItems(this.state.items)}) has to be called only after this.state.clickedItemId is updated. To achieve this, the setState method can receive a callback function as a second parameter. The code snippet is the following:
toggleSelection (id, e) {
if(!_.includes(this.state.selectedIds, id)) {
this.setState((state) => ({
clickedItemId: id,
selectedIds: state.selectedIds.concat(id)
}), () => this.setState({mappedItems: this.mapItems(this.state.items)}));
}
}
In this case, at the time the mapItems function is executed all data from the state that is needed here will be already updated:
mapItems (items) {
return items.map(item => {
return (
<li key={item.id} onClick={(e) => (this.toggleSelection(item.id, e))}>
<span>{item.title}</span>
<span>{this.state.clickedItemId}</span>
<Tick ticked={this.state.clickedItemId === item.id}/>
<span className="close-item"></span>
</li>
);
});
}

TypeError: Cannot read property "map" of undefined

I'm following the ReactJS AJAX and APIs tutorial. I wrote a simple API in Spring, then a React component to consume that API at http://localhost:8080. The API currently returns a list of two items as follows:
[
{brand:"Asus", make:"AAA"},
{brand:"Acer", make:"BBB"}
]
Here's what my component looks like:
import React from 'react';
import ReactDOM from 'react-dom';
import { environment } from '../environment/environment';
export class ComputerList extends React.Component {
constructor(props) {
super(props);
this.state = {
error: null,
isLoaded: false,
items: [
{brand: null, make: null}
]
};
}
componentDidMount() {
fetch("http://localhost:8080/computers")
.then(res => res.json())
.then(
(result) => {
// correctly displays the results
console.log(result);
this.setState({
isLoaded: true,
items: result.items
});
},
(error) => {
this.setState({
isLoaded: true,
error
});
}
)
}
render() {
const { error, isLoaded, items } = this.state;
if(error) {
return(<div>Error: {error.message}</div>);
}
else if(!isLoaded) {
return(<div>Loading...</div>);
}
else if(items) {
console.log(items);
// error here: cannot read property "map" of undefined
// because this.state.items is undefined somehow?
return(
<ul>
{items.map(item => (
<li key={item.make}>{item.brand} {item.make}</li>
))}
</ul>
);
}
}
}
At line 24, the results are successfully retrieved and logged.
But at line 54, when I try to map each result to a <li> item, the TypeError is thrown because items is somehow undefined? I've followed the answer to a similar question by initializing items at line 12, and checking items at line 48, to no avail.
How can I fix this?
This is likely due to the type of items being something other than an array, which is why the map() method would not be defined.
For a more robust render() method, you can replace else if(items) { with else if(Array.isArray(items)) {, which should protect against the error message you're seeing:
render() {
const { error, isLoaded, items } = this.state;
if(error) {
return(<div>Error: {error.message}</div>);
}
else if(!isLoaded) {
return(<div>Loading...</div>);
}
else if(Array.isArray(items)) { // <-- update here
console.log(items);
// error here: cannot read property "map" of undefined
// because this.state.items is undefined somehow?
return(
<ul>
{items.map(item => (
<li key={item.make}>{item.brand} {item.make}</li>
))}
</ul>
);
}
}
Hope that helps!
Thanks to #DacreDenny for the advice :)
Line 27: items: result.items. This line expects the response to contain an Object named "items".
But my API only returns an Array of objects. So I changed the line to
Line 27 to: items: result. This saves the entire array to state.items. Then it can be properly mapped and rendered.
JUst do this. Avoid if loops in render instead use && operator or ternary operator directly in return to manage if checks
return(
<ul>
{!isLoaded && <div>Loading...</div>}
{error && !isLoaded && <div>Error: {error.message}</div>)}
{!isLoaded && items && items.map(item => (
<li key={item.make}>{item.brand} {item.make}</li>
))}
</ul>
);
Also your make key in object initially contains null value hence you cannot assign that as a key. Instead what you can do is try to generate unique id for object in your array from the backend or else use something like below
return(
<ul>
{items && items.map((item, index) => (
<li key={"key"+index}>{item.brand} {item.make}</li>
))}
</ul>
);
Excuse me for typo issues because I am answering from my mobile

Looping dynamic server response in React render

I'm developing some SPAs in React and I came across with this issue several times; I eventually solved it in UGLY ways like the one posted in the code below.
I feel like I'm missing something obvious but I really can't figure out a more elegant (or even right) way to accomplish the same result, can you help me?
class Leaderboard extends React.Component {
constructor(props) {
super(props);
this.state={
lb:{},
leaderboard:""
};
this.leaderboardGet = this.leaderboardGet.bind(this);
this.leaderboardSet = this.leaderboardSet.bind(this);
this.lblooper = this.lblooper.bind(this);
this.lbconstruct = this.lbconstruct.bind(this);
}
leaderboardGet(callback){ //server call which returns data
axiosCall.get('leaderboard.php',{params:{user:this.props.user}})
.then((response)=>{
var arr=response.data;
callback(arr);
})
.catch((error)=>{
console.log(error);
})
}
leaderboardSet(a){ //puts in lb object the results of the server call and calls lb looper
this.setState({lb: a});
this.lblooper();
}
componentWillMount(){
this.leaderboardGet(this.leaderboardSet);
}
lblooper(){ //the ugliness itself: loops the data in lb object, and pushes it into an "html string" in lblconstruct function
Object.entries(this.state.lb).forEach(
([key, value]) => this.lbconstruct(`<div class="leaderblock ${value.active}"><div class="leaderscore">${value.pos}) </div><div class="leadername">${value.usrname}</div><div class="leaderscore dx">${value.pts} <i class='fa fa-trophy'></i></div></div>`)
);
}
lbconstruct(s){
this.setState({leaderboard:this.state.leaderboard+=s});
}
render() {
return (
<div>
<div className="leaderboard">
<div dangerouslySetInnerHTML={{__html:this.state.leaderboard}}/>
</div>
</div>
);
}
}
Basically, if I have server data which has to be put inside html format for N loops, i couldn't find another way, so I'm wondering where I'm wrong.
Output your data into react elements in your render function:
class Leaderboard extends React.Component {
constructor(props) {
super(props);
this.state={
leaderboard: {},
};
}
componentWillMount(){
axiosCall.get('leaderboard.php', { params: { user:this.props.user } })
.then(response => this.setState({ leaderboard: response.data }))
.catch(console.log)
}
render() {
const { leaderboard } = this.state
return (
<div>
<div className="leaderboard">
// .map returns a new array, which we have populated with the react elements
{ Object.keys(leaderboard).map((key) => {
const value = leaderboard[key]
return (
<div key={key} class={`leaderblock ${value.active}`}>
<div class="leaderscore">{value.pos}</div>
<div class="leadername">{value.usrname}</div>
<div class="leaderscore dx">
{value.pts}
<i class='fa fa-trophy'></i>
</div>
</div>
)
}) }
</div>
</div>
);
}
}
Doing it like this is what allows react to work, it can keep track of what elements are there, and if you add one, it can see the difference and just adds the one element to the end, rather than re-render everything.
Also note that if you are only fetching data once, it may make sense to use a "container" component which fetches your data and passes it in to your "dumb" component as a prop.

Categories

Resources