Map is not a function : {} JSON array - javascript

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

Related

How do I set an array of objects to state in React?

So I am doing an SQL query in my node/express backend and sending the result to my React front end through JSON. I am console.log to see what info I am sending and that seems to be in order. However, I can't seem to figure out how to set this array of objects to my state. I am getting this error:
"Objects are not valid as a React child (found: object with keys {Title}). If you meant to render a collection of children, use an array instead."
Here is the component I am working with:
import React, { Component } from 'react'
export class ShoppingCart extends Component {
state = {
cartItems: ['Title 1']
};
displayCart = () => {
console.log('call it')
console.log('hello from displaycart');
fetch('http://localhost:5000/fillCart')
.then((res) => res.json())
.then((json) => {
this.setState(state => {
const cartItems = state.cartItems.concat(json.Title);
return {
cartItems
};
});
})
}
componentDidMount() {
this.displayCart();
}
render() {
return (
<div className="ShoppingCart">
<h3>This is your shopping cart</h3>
<ul>
{this.state.cartItems.map(item => (
<li key={item}>{item}</li>
))}
</ul>
</div>
)
}
}
export default ShoppingCart
My goal is to set the json.Title (right now holding multiple titles) into the cartItems State array I have in my state, then display each title through render() in a list. However, I am stuck as to how to accomplish this.
Also, here is what the json data looks like that I send from the Node/express backend:
[
RowDataPacket { Title: 'The Tragedy of The Korosko' },
RowDataPacket { Title: 'Two in the Far North' }
]
To clarify, at this point my problem is not displaying the information, but rather setting it to the state in the first place.
Any suggestions sure would be appreciated! I've been stuck on this for several days. Thanks in advance!!!
You need to extract all titles from the json response.
This is what the response format looks like:
{
Title: [{Title: ''}, {Title: ''}]
}
Your top level json.Title is an array with objects.
So the .Title property does not exist on json.Title, but on each of the objects inside of the array.
We can use map to pull out what we need.
Your setState could look something like this:
this.setState(state => {
// All titles in an array
const titles = json.Title.map(row => row.Title)
// Add to existing ones
const cartItems = state.cartItems.concat(titles);
return {
cartItems
};
});
Oskar Hane had an answer that was pretty close. Here is his answer with a slight modification:
.then((json) => {
this.setState(state => {
const titles = json.Title.map(row => row.Title)
const cartItems = state.cartItems.concat(titles);
return {
cartItems
};
});
})
}
The change is in json.Title.map. It is necessary to map through an array; however, json.map is not mapping through an array because json is not an array. json.Title is the array.
Use Array Spreading with current state and the JSON in response. Code will be like bellow:
displayCart = () => {
fetch('http://localhost:5000/fillCart')
.then((res) => res.json())
.then(json => {
const newItemsArry = json.map(itm=> itm.Titles)
this.setState({cartItems: [...this.state.cartItems, newItemsArry]}))
}
}

How to convert a list of arrays into a nicely packaged state?

I have an API call returning a list of breweries. I want to convert it from its current format into my React state container. Something like:
state = {
breweries: [ {name: Foo,
long: 45,
lat: -39.239},
{name: Bar,
long: 47,
lat: -27.394}
]
}
I've managed to get it into an array of Javascript objects (I think?) in my console.log. See the pic below.
https://imgur.com/a/1M16hXC
I think I have to do something with the .map() function but I'm not sure what. Currently my code reads as follows:
class App extends Component {
state = {
breweries: null
};
componentDidMount() {
const breweries = axios
.get("https://api.openbrewerydb.org/breweries")
.then(response => response.data)
.then(list => this.setState({ breweries: list }));
}
printState = () => {
console.log(this.state.breweries);
};
render() {
const response = axios
.get("https://api.openbrewerydb.org/breweries")
.then(response => console.log(response));
return (
<div className="App">
{/* <button onClick={this.printBreweries}>Click Me</button> */}
<button onClick={this.printState}>Also click me</button>
</div>
);
}
}
Currently the best I can do is shown in the code above. ".then(list => this.setState({ breweries: list }))" causes console.log(this.state.breweries) to output the array of objects, but it's not quite what I want.
Help?
Yep, map will help you transform each item in the list e.g.
axios
.get("https://api.openbrewerydb.org/breweries")
.then(res => res.data.map(({ name, long, lat }) => ({ name, long, lat })))
.then(list => this.setState({ breweries: list }));
Note - the first then is making use of parameter destructuring, object initializer shorthand syntax and leveraging arrow functions ability to return a default result by parenthesising the body, in this case an object literal is being returned

Render an object from an array of objects in React

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

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

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