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

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

Related

React: Accessing an objects properties within an array that I have mapped

I'm trying to make my first real React app and am pulling information from a database, updating the state to set that info as an array, and then trying to access the properties of the objects in the array.
function App() {
const [students, setStudents] = useState([]);
function fetchStudentInfo() {
fetch('https://api.hatchways.io/assessment/students')
.then(response => {
return response.json();
})
.then(data => {
const transformedStudentData = data.students.map(studentData => {
return {
imgURL: studentData.pic,
fullName: `${studentData.firstName} ${studentData.lastName}`,
email: studentData.email,
company: studentData.company,
skill: studentData.skill
}
});
setStudents(transformedStudentData);
});
}
fetchStudentInfo();
return (
<div> {console.log(students[0].fullName)}
<h1>Student Assessments</h1>
</div>
)
}
export default App;
I know I shouldn't be console.logging the info I'm trying to get, but I'm not even sure how to use the console in React to find out how to access my variables. Anyway, I get "TypeError: Cannot read properties of undefined (reading 'fullName')" as an error and it fails.
I'm really trying to pass down the array as properties to be used in my components, but I've taken out code to try and simplify the problem for myself and this is where I've hit a wall.
On the first render your students state is an empty array because that's how you've initialised it. On that first render you can't access the first index (because the data hasn't been fetched, or the state updated), and you can't access a property of an element that doesn't exist yet.
So you need a condition in there that renders: 1) if students.length is zero return a loading message (for example) 2) otherwise map over the students array which you now know exists to produce a list of student names.
Here's a working example that uses useEffect to add an array to state after three seconds simulating your API fetch. You should be using useEffect like this for your fetch (with an empty dependency array) instead of calling your function directly in the component.
const { useEffect, useState } = React;
const json = '[{ "fullName": "Bob" },{ "fullName": "Karen" },{ "fullName": "Rick" }]';
function mockApi() {
return new Promise((res, rej) => {
setTimeout(() => res(json), 3000);
});
}
function Example({ data }) {
const [ students, setStudents ] = useState([]);
useEffect(() => {
function getData() {
mockApi()
.then(json => JSON.parse(json))
.then(data => setStudents(data));
}
getData();
}, []);
// Initial log will be `[]`
// Second log will be the updated state stringified
useEffect(() => {
console.log(JSON.stringify(students));
}, [students]);
// No data, so return the loading message, or spinner
if (!students.length) return <div>Loading: wait 3 seconds</div>
// Data! `map` over it to produce the list
return (
<ul>
{students.map(student => {
return <li>{student.fullName}</li>;
})}
</ul>
);
};
ReactDOM.render(
<Example />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Map object with URL to return object

I loop through this array like this:
{props.choosenMovie.characters.map((characters) => (
<p>{characters}</p> /* This displays the URL of course */
))}
These URL's include a name object which is what i want to display,
what is the best practice to do this?
This is how it is displayed on my application, but the desire is to display the name object from the URL's.
In useEffect, map thru your array of urls and make the api call and store the promises in an array. Use promise.all and update the state which will cause re-render.
In render method map thru the updated state and display the names.
see working demo
Code snippet
export default function App() {
const [char, setChar] = useState([
"https://swapi.dev/api/people/1/",
"https://swapi.dev/api/people/2/"
]);
const [people, setPeople] = useState([]);
useEffect(() => {
const promiseArray = [];
char.forEach(c => {
promiseArray.push(fetch(c).then(res => res.json()));
Promise.all(promiseArray).then(res => {
console.log("res", res);
setPeople(res);
});
});
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{people.map((p, i) => {
return <p key={i}>{p.name}</p>;
})}
</div>
);
}
I was working with that API some time ago, and the way I approached it (to display the names etc) was with Promise.all
so the snipped looked like
axios.get(`https://swapi.dev/api/${this.props.match.path.split('/')[1]}/${this.props.match.params.id}/`).then((res) => {
let characters = []
// get all characters in the movie data
let characterPromises = []
res.data.characters.forEach((character) => {
characterPromises.push(axios.get(character))
})
// Create list with all characters names and link to page
Promise.all(characterPromises).then((res) => {
res.forEach((character, i) => {
characters.push(<li key={i}><Link to={`/${character.data.url.split('api/')[1]}`}>{character.data.name}</Link></li>)
})
this.setState({
characters
})
})
})
}
then I just used the characters lists (from state) in the render method

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

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

Mapping an array of Objects having user-id and getting user-details from this user-ID

I have an array of Objects like
const array = [{
text: 'some text',
userID: '1'
},
{
text: 'another text',
userID: '2'
}
]
I have to map over this array in React and also get the user details like username & user-dp through the userID. How should I do it. I am using React, MongoDB, NodeJS, Express.
I have tried calling the api to get user-details while mapping the array in my frontend code. It does not render properly as I learned later that we should not call api in the render() method.
Well your question is not complete, but I will give it a try...
Assuming you have:
const array = [{
text: 'some text',
userID: '1'
},
{
text: 'another text',
userID: '2'
}
];
You can do
async componentDidMount() {
// I dont know where the array come from, ill assume it comes from the props...
const { array } = this.props;
// Promise.all returning an array of userDetails in the same order than the given array
const userDetails = await Promise.all(array.map(({userID}) => fetch(`apiUrl/${userID}`);
this.setState({ userDetails })
};
And then you will have access to your details in the render by accessing to your state.
I see this thread is old but i'd like to share my answer anyway.
Since your working with react i'd make a component for every render. Give the component the userId as a prop and use useEffect and useState within the component to fetch the user with the given id and store it in the components state.
So in your rendering view it'll look like:
return (
{
array.map(item => {
return <CustomComponent item={item} />
})
}
And your component will look like:
const CustomComponent = ({ item }) => {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(item.userID, setUser);
}, [])
return (
<div>
{user && <p>{user.something}</p>}
<p>{item.text}</>
</div>
);
}

Categories

Resources