Having trouble displaying object property - javascript

I'm building a small pokedex using PokeAPI. I'm retrieving data from an endpoint and building my own object. The function that I've built for the latter runs on page load as follows:
$: pokemon = []
//fetch pokemon from PokeAPI
function getPokemon(){
let index = 0
fetch("https://pokeapi.co/api/v2/pokemon?limit=151") //
.then(response => response.json()) //
.then(allpokemon => {
allpokemon.results.map((poke) =>
pokemon = [...pokemon, {
index: index++,
name: poke.name
}]
)})
}
//populate pokemon array on page load
getPokemon()
I'm able to console log the object and traverse it in devtools, and also able to JSON stringify it and display within html. But whenever I try to retrieve a specific attribute, I receive a 500 error. For instance, I'm trying to display the current pokemons name in an h1 tag :
<h1>{pokemon[currentPokemon].name</h1> // this doesn't work
whereas
<h1>{pokemon[currentPokemon]</h1> //this does
This can't be too complicated of a fix, I just don't know what I'm missing, because I've been traversing objects for what I thought was the correct way for years now. Thank you.

This line is missing a curly brace at the end
<h1>{pokemon[currentPokemon].name</h1>
Besides that pokemon will initially be an empty array, so independent of currentPokemon the value will be undefined and accessing the key name will give the error Cannot read properties of undefined (reading 'name') which could be solved by adding a questionmark (Optional chaining)
<h1>{pokemon[currentPokemon]?.name}</h1>
There's no need for the $:, just let pokemon = []
.map() returns an array, so instead of setting pokemon inside the function better assign it the result. The index would be available as second parameter inside .map()
.then(allpokemon => {
pokemon = allpokemon.results.map((poke, index) => {
return {
name: poke.name, // or ...poke, to keep all fields
index
}
})
Writing the index in the data might not be neccessary if an #each loop is used to display the data
{#each pokemon as p, index}
<p>
{index} - {p.name}
</p>
{/each}

Try this:-
<h1>{pokemon[currentPokemon]['name']}</h1>

Related

React setState, using array methods in callback function

I am currently following a React course on Scrimba on creating a web app for taking notes.
The problem requires me to bump a note to the top of the note list every time it's updated.
The notes are initialised through useState as follows:
const [notes, setNotes] = useState([])
The array consists of the individual notes as objects with an id and body
Every time an onChange is triggered, the following function is ran:
function updateNote(text) {
setNotes(oldNotes => {
let updated = oldNotes.map(oldNote => {
return oldNote.id === currentNoteId
? { ...oldNote, body: text }
: oldNote
})
const currNoteIndex = updated.findIndex(
note => note.id === currentNoteId
)
console.log(currNoteIndex)
updated.unshift(updated.splice(currNoteIndex, 1))
return updated
})
}
However, I keep getting an error as shown in the image.
It's very unclear to me where the problem lies, but I'm thinking it has to do with the array methods.
Any explanation for this issue would be greatly appreciated!
Credits to jsN00b for the answer:
array.splice returns an array, not the object.
Since that array is inserted to the start of the array containing the objects, there will be an error when updateNote() is called again.

Weird behavior on array map after state update

Hope you're all keeping well in the current climate!
I'm working with Firebase and ReactJS at the moment and have encountered an odd behavior with the state update in react and the array map functionality in JavaScript. The code below shows my logic for listening to updates from the firestore database:
listenForContentChanges = async () => {
let content = [];
await db.collection('clients').doc(this.props.client)
.collection('projects').doc(this.props.id)
.collection('content').orderBy('created').onSnapshot(querySnapshot => {
querySnapshot.docChanges().forEach(async change => {
if (change.type === 'added') {
content.push({
id: change.doc.id,
uploaded: change.doc.data().created.seconds
});
}
if (change.type === 'removed') {
content = content.filter(arrayItem => arrayItem.id !== change.doc.id);
}
});
this.setState({
content: content,
addingContent: false
});
});
}
As you can see, an array is populated with 'content' information just the ID of the document and a field containing the time in seconds of when that document was created. This gives me an array back:
0: {id: "SZ4f0Z27rN2MKgXAlglhZVKDsNpKO6", uploaded: 1586323802}
I need this array sorted so the most recent document comes first and because Firebase doesn't offer this as a query parameter (you can only sortBy and not change the direction) I copy the array to a new array and then loop over that instead to get everything in the correct order:
const sortedArr = [...this.state.content];
sortedArr.reverse();
/// IN THE RETURN: ///
{sortedArr.map((content, index) => (
<Content key={index} />
))}
This works okay with no issues. My problem is that now when a new element is added/one is taken from the state array this.state.content and the component is re-rendered, I am seeing a weird behavior where the last elements, instead of showing the new data, seem to duplicate themselves. Please see an example timeline below:
As you can see, when there is a new document added in firestore, the code shown above fires correctly, pushing a new array element onto the state and re-rendering the component. For those who are interested, yes the state is being correctly updated, this is what is being logged inside the render function after the state update:
0: {id: "x07yULTiB8MhR6egT7BW6ghmZ59AZY", uploaded: 1586323587}
1: {id: "SZ4f0Z27rN2MKgXAlglhZVKDsNpKO6", uploaded: 1586323802}
You can see there index 1 is new which is the document that has just been added. This is then, reversed and mapped in the return() function of the render() but causes the weird behavior shown in the image above. Any help on what may be causing this would be a great help, I've been banging my head against the wall for a while now!
Using array index as react key fails when the rendered array is mutated (i.e. unstable). React sees the new length, but the key for the first element is still the same so it bails on rerendering (even though it's the new element).
Try instead to always use unique keys within your dataset, like that id property.
{sortedArr.map((content, index) => (
<Content key={content.id} />
))}

Javascript saying object value is undefined. What am I doing wrong?

I'm trying to display the value of an object within an array of Github repositories (retrieved from a parsed JSON) in my React app but I'm getting an error that the browser can't read property key of undefined.
I'm able to console.log the JSON data and the objects within it but if I try to log the values within those objects, I get the error.
const App = () => {
const [repoList, setRepoList] = useState({});
useEffect(() => {
fetch("https://example.com/api")
.then(res => res.json())
.then(data => setRepoList(data));
});
return (
<div>{repoList[0].id}</div>
)
}
If I put JSON.stringify(repoList) within the div element, it shows the data.
If I put JSON.stringify(repoList[0]) within the div element, it also shows the data.
But if I put something like repoList[0].id nothing appears. This happens with each key listed. And I can confirm that the key is there. What am I doing wrong?
Try this.
const [repoList, setRepoList] = useState([]); // Change to array not object.
...
return repoList.length ?
(<React.Fragment>{repoList[0].id}</React.Fragment>)
: (<span>Loading ....</span>)
If your div does not have styles, better user Fragment, it result in better performance.
You're calling async functions to populate the repo list. The first time the return tries to use repoList[0].id, there is no repoList[0]. You may want to initialize repoList to a non-error-ing dummy object when you set it up with useState({})

How to get value from array of objects?

I have an react app, and using redux and props to get array of objects into my component, and i am getting them. But i can't access particular property inside of one of objects that are in that array.
With this:
console.log(this.props.users)
I get listed array with all objects inside it. But when i need to access particular object or property of that object, for example:
console.log(this.props.users[0])
console.log(this.props.users[0].name)
I am getting error:
Cannot read property '0' of undefined
But when I iterate through array with map() method i have access to it, it works. Why can't i access it normally?
You are trying to access properties of this.props.users before it has loaded. Your component renders without waiting for your data to fetch. When you console.log(this.props.users) you say that you get an array, but above that, it probably logs undefined at least once when the component renders before this.props.users has loaded.
You have a couple of options. You can either do this at the very top of your render method to prevent the rest of the code in the method from executing:
if (!this.props.users) return null;
Once the data is fetched and props change, the render method will be called again.
The other option is to declare a default value, of an empty array for users in your reducer.
Might be when you are executing that line this.props.users is undefined. Check the flow where you have added console.log(this.props.users[0])
const App = () => {
const example = () => {
const data =[{id:1 ,name: "Users1", description: "desc1"},
{id:2 ,name: "Users2", description: "desc2"}];
return (
<div>
{data.map(function(cValue, idx){
console.log("currentValue.id:",cValue.id);
console.log("currentValue.name:",cValue.name);
console.log("currentValue.description:",cValue.description);
return (<li key={idx}>name = {cValue.name} description = {cValue.description}</li>)
})}
</div>
);
}
return(
<p style = {{color:'white'}}>
{example()}
</p>
);
}
export default App;

An array with objects, within an object is undefined

I have an array that contains objects, inside an object.
I can console.log the first object and the array, but when i try to access the objects within the array or use the map-function on the array i get an error that says "Can't read property of undefined".
I have thoroughly searched SO and other sites for similar problems and found some but no answers seems to work for me.
The object looks like this:
{
answers: [{…}],
createdAt: "2019-01-23T10:50:06.513Z",
nested: {kebab: "jjjj", sås: 2, sallad: "kkk"},
text: "weaxcc",
/* etc... */
}
And i can access it using: this.state.data
I want to access objects inside the answers-array like:
this.state.data.answers[0].text
or even :
this.state.data.answers.map().....
But that gives me 'Cannot read property '0' of undefined. The answers-array is not empty.
Any help is appreciated!
EDIT
This is how the objects ends up in my state.
getQuestionFromDb = () => {
axios.get(`http://localhost:3000/questions/${this.state.id}`)
.then(res => this.setState({
data: res.data
}));
};
This function is called in the ComponentDidMount()-method.
Here is my render function (the console.log is causing the error):
render() {
return (
<div className="main-content">
<h2>{this.state.data.text} </h2>
{console.log(this.state.data.answers[0].text)}
<p>Introducing <strong>{this.state.id}</strong>, a teacher who loves teaching courses about <strong>{this.state.id}</strong>!</p>
<input
type="text"
onChange={e => this.setState({ message: e.target.value })}>
</input>
<button onClick={() => {this.handleAnswerPost(this.state.message)}}>Answer</button>
</div>
);
}
}
componentDidMount is getting called when your component becomes part of the DOM but the call you do to populate your state is async due to XHR, which may take 100-300ms more to get the data in your component, so this.state.data.answers won't be available in the initial render() cycle.
since you mentioned using a loop, I suggest setting an initial state shape like
this.state = {
data: {
answers: []
}
}
your initial render won't have anything to loop but as soon as it resolves the data and sets the new state, it will render correctly.
alternatively you can
return this.state.data.answers.length ? loopItemsHere : <div>Loading..</div>
obviously, loopItemsHere can be anything you write to show the answers.
This might not be working when data doesnt contain answers[] at the very first mount for a component.
You may wanna check for your array's existance as following:
const { data } = this.state;
data.hasOwnProperty('answers') && console.log(data.answers[0]);
The reason we can't access object key in Javascript, usually it is because the variable/value is not the type of Object. Please ensure that this.state.data type is Object. You can try to console.log( typeof this.state.data ). We should expect that it is output object. If the type is string, you should parse it first with JSON.parse().
There may be a number of reasons that could cause the data object not to be shown:
The object was not in state at the time of it being called. This may be caused by an async operator not loading in the data. E.g. if you are requesting for the object from the database, chances are that at the time of making a call to retrieve the data (this.state.data), the response had not been given.
The object may not have been parsed into string. Try running console.log(typeof this.state.data). If the output is string, then you may have to parse it. If the output is undefined, then point one is valid

Categories

Resources