Acessing properties of an object in an array in React - javascript

So I've been trying to figure this one out for a while and I'm really hoping someone would be able to help
I'm creating an array and storing a list of objects in it like this.
const heroPost = postList.filter(post => post.hero);
when I console log heroPost I get the following result.
I'm trying to access the url property in this object as follows.
console.log(heroPost[0].url);
But I keep getting this undefined error.
I'm not really sure what's going wrong here. I am using an async function to get data from a database which I'm guessing might be a problem but I'm outputting data from the 'postList' in jsx which I've used to retrieve data into the 'heroPost' so I'm uncertain how it can be the problem. Any help or ideas would be greatly appreciated.
Thank you!
Edit:
This is the portion of the component that uses the 'postList' to render data. Which works fine.
<div className="posts-container">
{postList.map((post) => (
<div key={post.id} className="post">
<h3 className="post-title">{post.title}</h3>
<div className="post-info">
<p className="author">{post.author}</p>
<p className="timestamp">{post.author}</p>
</div>
<p className="content">{post.body}</p>
<img src={post.url} alt="" />
</div>
))}
</div>
What I'm trying to do now is to use the heroPost to render data into the hero section of the page as follows but it is still throwing the undefined error.
<div className="hero">
<img src={heroPost[0].url} alt="" />
</div>
Thank you for all the comments and answers, I understand the problem now. Still don't really get why it works in the 'postList' instance and not here. Is there a way I can do this without directly trying to go to the file where I retrieve the data and add it into that function?

Since you are using an async function to get the data, when the component that consists this code, renders for the first time, it will not have the data it needs to render the component.
Hence 'undefined'
You can resolve this issue by conditionally rendering the component. That is, render the component only after the data is available.
Since you haven't shared the component code, you can refer this react doc to help with the issue : Conditional Rendering
Edit: following is how the condition would work. This is assuming postList is a state variable and a re-render will be triggered when it's value is changed:
For the second snippet you shared:
<div className="hero">
{(heroPost && heroPost[0])?<img src={heroPost[0].url} alt="" />:'No data'}
</div>

Simple answer: you get those error because it's undefined
If you look at you console.log you can see an empty array at the first line
That's because when you first render your component postList is an empty array.
Only after you fetch your data and populate you array you can access it
you can try to do this
useEffect(() => {
setHeroPost(postList.find(p => p.hero))
}, [postList])

Related

Render an object's key and values via a component and v-for in VueJS

Essentially I have an object whose keys and values are altered via other functions.
app=new Vue({
...
data:{
myObject:{"key1":"value1","key2":"value2"}
}
})
I wrote a component which receives them as props and displays them.
Vue.component("my-component",{
props:['k','v'],
template:`
<div>{{k}}:{{v}}</div>
`
})
Now, when I write this:
<my-component v-for="(value,key) in myObject" v-bind:k="key" v-bind:v="value"></my-component>
And execute app.myObject['someKey']='some value' nothing happens and without any console message, even when in development mode.
Where am I going wrong? Or is there a better way to render an object's key and values via a component?
Edit:
Something I observed, when the object is already is populated in the data object, it renders perfectly. However when an outside function modifies it, the changes are not reflected unless I modify a key's value which value which was already present in the object.
Edit2:
https://jsfiddle.net/agentrsdg/xs635ndk/8/
Loop for children in Object like below.
<div v-for="(value, key, index) in object">
So for your codes.
<my-component
v-for="(value, key) in myObject"
v-bind:key-prop="key" // prop name in my-component is keyProp but use key-prop in the parent component
v-bind:value-Prop="value"
/>
Here is the JSFiddle for my answer.
So I thought I discovered a bug and an issue on github(https://github.com/vuejs/vue/issues/10611), and they were quick to respond with the solution. The above not working is a limitation of JavaScript itself. So one should use,
app.$set(app.myObject,'newKey','newValue)
or
Vue.set(app.myObject,'newKey','newValue')
More details here : https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats

VueJS: `v-for` not rendering elements

As much as I'd like to share just share the relevant code in a simplified version, I can't. I have a slightly overdone todo app made in Vue. Here's the link to the repo: https://github.com/jaiko86/subtasks
The todo items are managed in the store:
export default {
state: {
workspaceIds: [], // IDs of workspaces
tasksById: {}, // <--- this is the tasks I have
detachedTask: null,
focusedTaskId: null,
currentWorkspaceId: null,
},
...
}
In the App.vue file, I have the following computed property that the v-for will run through:
computed: {
taskIds() {
const { currentWorkspaceId, tasksById } = this.$store.state;
if (this.showOnlyLeafSubTasks) {
return Object.values(tasksById)
.filter(task => !task.subTaskIds.length)
.map(task => task.id);
} else if (currentWorkspaceId) {
return tasksById[currentWorkspaceId].subTaskIds;
}
},
},
Basically, the computed property will only return a list of tasks that are relevant to certain conditions, such as the workspace that I'm in. Each task has a unique ID, but for the sake of debugging, I've made it so that each task has the ID of the format task-#, and the task's input will have its task ID as a placeholder.
Here's the template in the App.vue:
<template>
<div id="app">
<!-- This is the top part -->
<WorkspaceNav />
<!-- this is the for-loop in question -->
<TaskWrapper v-for="id in taskIds" :key="id" :id="id" :depth="0" />
<!-- this is the filter that's on the right side -->
<TaskFilters />
</div>
</template>
The problem is that it won't render the items that the computed property returns.
I am failing miserably in trying to understand why there's a discrepancy between what's shown in the vue devtool and the console, and the view.
Here is the rendered view:
Here is what's in the vue dev tool:
Here's what's printed in the console when I select the <App> component in the dev tool, thereby making it accessible via $vm0.taskIds:
["task-1", "task-2"]
Here's my custom function that prints the tasks hierarchically:
> printTree()
task-0
task-1
task-2
Here's the DOM for the relevant section of the code for the moment:
<div id="app">
<div class="workspace-nav">
...
</div>
<div data-v-76850a4a="" data-v-7ba5bd90="" class="leaf">
<div data-v-76850a4a="" class="main-task depth-0">
<!---->
<div class="progress-indicator">
<div class="checkmark gray-checkmark"></div>
</div>
<input placeholder="task-1" />
</div>
<div class="sub-tasks"></div>
</div>
<div class="filters">
...
</div>
</div>
So it's clear that no matter where I look, taskIds is returning a list of two items, but it's only rendering the first one.
Here's one possible explanation...
At the point that the computed property returns its array the array only contains one item. You can confirm this in several ways. e.g. Try putting this in your template:
{{ taskIds }}
That array could subsequently change so that it contains two items but if the changes don't trigger the reactivity system them re-rendering will not occur.
Best guess is that tasksIds is returning tasksById[currentWorkspaceId].subTaskIds. That leaves two likely possibilities. Either subTasksIds isn't reactive, or it is being modified in a way that doesn't trigger the reactivity (e.g. direct modification by index).
Taking a look at your store code I don't see any obvious examples of the latter. However, this line from createTask does seem suspiciously like the former:
state.tasksById[task.id] = task;
This is adding task to an existing object under a (potentially) new key. This is one of the reactivity caveats, you can't add new properties to objects directly:
https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
Instead you'd need to use Vue.set:
Vue.set(state.tasksById, task.id, task);
To better understand what's going on try adding the following logging to the end of createTask:
console.log(state.tasksById);
If you dig into the objects/arrays in the console you should see evidence of the Vue reactivity system. You may see references to Observer. Properties will have getters and setters and you need to click to see the property values. However, this will only happen if the object/array is reactive. Tasks that you add directly, without Vue.set, will just appear as normal objects/arrays in the console. It's worth taking some time to learn what the difference looks like as it makes debugging these kinds of problems much easier if you know what to look for.

Redux container cannot display the reducer handling an API call

I have been building a basic weather app using React, Redux, Redux-promise and Axios (handling an API for the current weather using openweathermap.org).
If the button gets clicked, the weather parameters of the city (Cecciola) should be displayed on the console.
The Action correctly retrieves the data and, thanks to Promise, it is passed onto the reducer as a normal payload.data, not a promise.
Then, the container responsible for the rendering of the city.name is connected to the reducer managing the weather (so that you can access it using this.props) but, if the button is clicked, the console.log(this.props.tempo....) says that the object is undefined. Why?
GitHub Repo link
Trying your repo, logging to the console this.props.tempo works just fine for me in the render method of ceciola component.
Where I see the error, is in your renderR() function. You're attempting to use diocane.city.name but there is no 'city' property for that object.
Try: <p>{diocane.name}</p> to get the name.
_______ UPDATE ________
Response to your comment: I pulled the latest version from the repo, and again, everything seems to work just fine when you click the button to retrieve the data. As the code is now, you are doing:
console.log(this.props.tempo[0])
So, on first load of the component, you have nothing in the props.tempo array, so you see undefined in the console. When you click the button, you now have a single object in the array and that log statement works just fine.
I changed your render() method to:
render() {
if (this.props.tempo.length > 0) {
console.log("TEMPO", this.props.tempo[0])
console.log("ID", this.props.tempo[0].id)
}
return (
<div>
{this.props.tempo.map(t => {
return <div key={t.id}>{t.name}: {t.main.temp} </div>
})}
</div>
);
}
And it logs out the expected information. You just need to confirm that the tempo prop has something in the array before attempting to access it. And then, when you do, make sure you're accessing the individual object(s) inside. I show an example of this in the return() method above: using map to iterate and return a new array of <div> elements with the tempo object info.

Debugger inside React Context API Consumer block - this is undefined

I am trying to debug a component using Context API Consumer inside render method on browser dev tools. If i place break-point inside Consumer block, i can't print props etc. on console dynamically as this is undefined. Normally running this works fine, but while debugging only value of this is undefined. Following is sample render method of component.
componentMethod = () => {
console.log(this.props.name); //Placing breakpoint here, value is this is defined
}
render() {
return (
<div className={styles.container}>
<div>
<h4>{this.props.name}</h4>
</div>
<div className={styles.block}>
<MyComponent.Consumer>
{
({ firstParam, secondParam }) =>
<AotherComponent
firstParam={firstParam}
secondParam={secondParam}
thirdParam={this.props.name}
/>
}
</MyComponent.Consumer>
</div>
</div>
)
}
I could be related fat arrow use here, but I am able to get value of this while using break-point in componentMethod. Is there a way to bind this for Consumer block?
Try this, however, your question doesn't give enough context on what you are trying to solve. It would be better if you shared the Provider implementation as well and where you use it.
render() {
const { name } = this.props;
return (
<div className={styles.container}>
<div>
<h4>{name}</h4>
</div>
<div className={styles.block}>
<MyComponent.Consumer>
{
({ firstParam, secondParam }) =>
<AotherComponent
firstParam={firstParam}
secondParam={secondParam}
thirdParam={name}
/>
}
</MyComponent.Consumer>
</div>
</div>
)
}
It looks like you are interested in knowing what your consumer is passing down to your component during execution. There are two ways to accomplish it.
Hard Way
Let us breakdown how the consumer works (using your sample). That may help you with finding the right place to debug.
in your render() method, you have a <MyComponent.Consumer> call. The new Context Consumer API is built on the render-prop pattern.
Important thing to remember about the render-prop pattern is that: it is a function call. This function should return something which react can consider while rendering the tree.
Since it is a function call, you can put your console.log statements before your element. You will have to add an explicit return statement though.
As to why it is undefined in your method. I am assuming that the componentMethod is not a lifecycle method, so it is possible that this or this.propsis undefined based on how you are invoking that method. I do not see it invoked anywhere in your render method.
Eas(y/ier) Way:
Use react dev tools browser extension. You can look up all components by name. On clicking them you can see the props and state and even the context (as far as I remember). You can even change the values and see react react to it!

React weird rendering behavior

Even though printing items logs a populated array before the return function, it doesnt really render anything. I know for a fact its not a problem with improperly displaying the html. So i got suspicious and stringified it inside the return function to see if indeed the data im logging is there and to my dread i realised it isnt. As shown in the code, within the return function i get an empty array!
class Derp extends Component {
constructor(props){
super(props);
mainStore.subscribe(this.render.bind(this));
}
render(){
var items = mainStore.getState().itemReducer.items;
console.log(items); //yields an array of items as expected
return (
<div>
<span>{JSON.stringify(items)} </span> //yields [] in the DOM !!!!!!!
//when it should yield the same as above, a fully populated array
{
items.map(item =>
<div key={item.id}>
{item.name}
</div>
)
}
</div>
)
}
}
I've done this numerous times succesfully but this time around i just cant figure out what could be wrong with it.. Thanks for taking the time.
EDIT 1: I know this will seem cringeworthy ( because it is ) but the component is listening to all state changes like so : mainStore.subscribe(this.render.bind(this)); so it should always have access to updated data.
P.S: I am aware of dumb vs clever components and that im not using ReactRedux, im just experimenting and trying a few different things for curiosity's shake. This is an self-imposed "study" kind of code. This isnt meant for production or a project.
Return the div from your map function:
items.map(item =>
return <div key={item.id}>
item.name
</div>
)
Try escaping the content you wish to render:
items.map(item =>
<div key={item.id}>{item.name}</div>
)
There are two problems I see with your code:
You are trying to return more than one element. If you're before react16, you need to wrap those two elements in a div or something. If you're on react16, you can return them in an array.
You item.name needs to be in curly braces. Any JS within JSX markup needs to have curly braces. (This is how they know it's JS and not markup).
react16+
return [
<span>{JSON.stringify(items)} </span>,
...items.map(item =>
<div key={item.id}>
{item.name}
</div>
)
]
< react16
return (
<div>
<span>{JSON.stringify(items)} </span>
{items.map(item =>
<div key={item.id}>
{item.name}
</div>
)}
</div>
)
It seems like the problem was calling render directly. Instead calling forceUpdate() works fine. I am not 100% why this happens but i suspect that calling render on it's own doesnt mean much as it would probably need to be called in a React context in the React pipeline. I might be horribly off and if so please flag me and describe it a little better that i currently am able to.
Thanks everyone for helping.

Categories