Why do I get Type Error when passing props in map? - reactjs

I can't seem to pass the 'name' prop to the component without a type error. I have an array of objects being passed in as {reslist} and each object in that array has a logo, and name value. I cant seem to pass name or logo into my return.
import React from 'react';
import './Resources.css'
import CodeCard from '../CodeCard/CodeCard.js'
const Resources = ({ reslist }) => {
return (
<div>
<h3 className='resCall'>Check out my Code!</h3>
<div className='resCards'>
{ reslist.map((i) => {
return ( <CodeCard
key={i}
name={reslist[i].name}
/>
);
})
}
</div>
</div>
)
}
export default Resources;
Here is the error
TypeError: Cannot read property 'name' of undefined (anonymous
function)
Here is my Resource List
const ResList = [
{
logo: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png',
name:'GitHub'
},
{
logo: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png',
name:'GitHub'
},
{
logo: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png',
name:'GitHub'
},
{
logo: 'https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png',
name:'GitHub'
},
]
export default ResList

Your map callback has incorrect parameters. The first parameter is value, 2nd is index (ref):
reslist.map((value, index) => {
return (<CodeCard
key={index}
name={reslist[index].name}
/>)
})
But of course, you can use value directly too:
reslist.map((value, index) => {
return (<CodeCard
key={index}
name={value.name}
/>)
})

I'm assuming reslist is an array of objects. When you call .map on an array of objects, the first parameter (in your example, i) is each object. The second parameter is the index. I think you're getting these two mixed up.
reslist.map((item, i) => (
<CodeCard
key={i}
name={item.name}
/>
))

Importing components don't need to have .js:
edit it with import { CodeCard } from '../CodeCard'
ps: please don't shot me

Related

Why when you delete an element out of react array, the inner element you pass it to remains

In a react component I have an array of things. I iterate through that array display the name of the thing in a plain div, then pass each element to another component to display details.
What's happening: if I delete an element from anywhere except the bottom (last array element) the header that is displayed in the main element containing the array is correct (the one I clicked "delete" on disappeared), but the "body" (which is another component) remains. Instead, the inner component is acting as if I deleted the last element of the array and kind of "moves" up the array.
It's hard to describe in words. See example below. Delete the top element or one of the middle ones and see how the header for the section starts not matching the contents.
I'm trying to understand why this is happening.
(EDIT/NOTE: State IS needed in the child component because in real life it's a form and updates the object being passed in. I Just removed the updating here to make the example shorter and simpler)
Example code (delete the middle element of the array and see what happens):
https://codesandbox.io/s/confident-buck-dodvgu?file=/src/App.tsx
Main component:
import { useState, useEffect } from "react";
import InnerComponent from "./InnerComponent";
import Thing from "./Thing";
import "./styles.css";
export default function App() {
const [things, setThings] = useState<Thing[]>([]);
useEffect(() => resetThings(), []);
const resetThings = () => {
setThings([
{ name: "dog", num: 5 },
{ name: "cat", num: 7 },
{ name: "apple", num: 11 },
{ name: "book", num: 1}
]);
};
const onDeleteThing = (indexToDelete: number) => {
const newThings = [...things];
newThings.splice(indexToDelete, 1);
setThings(newThings);
};
return (
<div className="App">
{things.map((thing, index) => (
<div key={`${index}`} className="thing-container">
<h2>{thing.name}</h2>
<InnerComponent
thing={thing}
index={index}
onDelete={onDeleteThing}
/>
</div>
))}
<div>
<button onClick={resetThings}>Reset Things</button>
</div>
</div>
);
}
Inner component:
import { useEffect, useState } from "react";
import Thing from "./Thing";
interface InnerComponentParams {
thing: Thing;
index: number;
onDelete: (indexToDelete: number) => void;
}
export const InnerComponent: React.FC<InnerComponentParams> = ({
thing,
index,
onDelete
}) => {
const [name, setName] = useState(thing.name);
const [num, setNum] = useState(thing.num);
return (
<div>
<div>Name: {name}</div>
<div>Num: {num}</div>
<div>
<button onClick={(e) => onDelete(index)}>Delete Me</button>
</div>
</div>
);
};
export default InnerComponent;
You are creating unnecessary states in the child component, which is causing problems when React reconciles the rearranged Things. Because you aren't setting the state in the child component, leave it off entirely - instead, just reference the prop.
export const InnerComponent: React.FC<InnerComponentParams> = ({
thing,
index,
onDelete
}) => {
return (
<div>
<div>Name: {thing.name}</div>
<div>Num: {thing.num}</div>
<div>
<button onClick={(e) => onDelete(index)}>Delete Me</button>
</div>
</div>
);
};
The other reason this is happening is because your key is wrong here:
{things.map((thing, index) => (
<div key={`${index}`}
Here, you're telling React that when an element of index i is rendered, on future renders, when another element with the same i key is returned, that corresponds to the JSX element from the prior render - which is incorrect, because the indicies do not stay the same. Use a proper key instead, something unique to each object being iterated over - such as the name.
<div key={thing.name}
Using either of these approaches will fix the issue (but it'd be good to use both anyway).
This is also wrong. You're removing everything except the index.
const onDeleteThing = (indexToDelete: number) => {
const newThings = [...things];
newThings.splice(indexToDelete, 1);
setThings(newThings);
};
Use filter:
const onDeleteThing = (indexToDelete: number) => {
const newThings = [...things].filter(
(thing, index) => index !== indexToDelete
);
setThings(newThings);
};

How to render custom elements for each item in an object (Map data structure) in React TS?

I've been using this method to render multiple custom elements from an array but this is my first time doing it using a map data structure. It compiles fine but renders nothing. I've set up a codesandbox here.
import "./styles.css";
import React from "react";
import ImageGrid from "./ImageGrid";
interface OnlineImage {
id: string;
url: string;
}
export default function App() {
const [onlineImages, setOnlineImages] = React.useState<Map<string, OnlineImage[]>>();
React.useEffect(() => {
let onlineImageMap = new Map<string, OnlineImage[]>();
// fake api call
onlineImageMap.set("randomImageSet1", [
{ id: "1", url: "https://picsum.photos/200" },
{ id: "2", url: "https://picsum.photos/200" }
]);
onlineImageMap.set("randomImageSet2", [
{ id: "1", url: "https://picsum.photos/200" },
{ id: "2", url: "https://picsum.photos/200" }
]);
// set state
setOnlineImages(onlineImageMap);
}, []);
return (
<div className="App">
<div>Below should render my custom ImageGrid for each item in map...</div>
<>
{onlineImages?.forEach((imageSet, category) => {
return (
<>
<div>Image Category: {category}</div>
<ImageGrid images={imageSet} />
</>
);
})}
</>
</div>
);
}
Hi Samuel: I think you should first convert the map to an Array and then use the Array.prototype.map method which actually returns a copy of the original array with your function applied to it.
As you probably already figured out the return statement does nothing within a forEach function (to be more exact it only stops the execution of the code but does not bring back anything into the outer context).
If you really want to use forEach you'll have to use an array or Object to catch it then use Object.entries/.map to iterate over it
const myMap = new Map(Object.entries({a: 1, b: 2}))
const myMapCaptureList = []
myMap.forEach((value, key) => {
myMapCaptureList.push([key, value])
}
// then you can use myMapCaptureList
myMapCaptureList.map(([key, value]) => <div>{key}: <span>{value}</span></div>);
But I would suggest that it is much easier to use the very helpful Array.from method that can help convert a Map into an array of key/value pair entries: [[key1, val1], [key2, val2]]. It is essentially equivalent to running Object.entries(someObject).
{Array.from(onlineImages || []).map(([category, imageSet]) => {
return (
<>
<div>Image Category: {category}</div>
<ImageGrid images={imageSet} />
</>
);
})}
You are using the .forEach method - which returns nothing, instead use .map that is identical but does return things
{onlineImages?.map((imageSet, category) =>
<>
<div>Image Category: {category}</div>
<ImageGrid images={imageSet} />
</>
)}

React component error message using filter method

Why am I getting a Type Error: Cannot read properties of undefined (reading 'filter') with the following React component? (I can console.log "squads" and "weekNo" and get the correct results)
import React from "react";
const Weekly = ({ squads, weekNo }) => {
return (
<>
<div>
{squads
.filter((squad) => squad.division === "NFC North")
.map((filteredDivision) => (
<li>{filteredDivision}</li>
))}
</div>
<div>
<p>{weekNo}</p>
</div>
</>
);
};
export default Weekly;
I'll add some comments to your code.
{squads
// 1
.filter((squad) => squad.division === "NFC North")
// 2
.map((filteredDivision) => (
<li>{filteredDivision}</li>
))}
Assuming squads is an array like below,
[{ division: 'a' }, { division: 'b' }]
After your statement 1, it gets all element with division set to "NFC North".
Now at your statement 2, remember it's still an array of objects. You can't print out an object directly. But you can print out one property of an object.
.map((squad) => (
<li>{squad.division}</li>
))
Hope this makes sense now.
NOTE: filter or map produces a new array.
Make sure the squads is valid at the first place. For example.
const Weekly = ({ squads, weekNo }) => {
if (!squads) return null
return (...)

React typescript recognizes array as object

new to react and js in general so it might be a dumb question but I can't sort it out.
I have a component like so:
export interface TableViewContainerProps {
header: WorthSummaryContainerProps
content: TableViewRowProps[]
}
export const TableViewContainer = (props: TableViewContainerProps) => {
console.log('content type is ', typeof(props.content))
console.log('content is ', props.content)
return (
<div id="tableview-container">
<div id="tableview">
<TableViewHeader {...props.header}/>
<TableViewContentList {...props.content} />
<div id="tableview-footer">
</div>
</div>
</div>
)
}
So when I print it it's an array of objects, all good.
TableViewContentList gets the content as props:
export const TableViewContentList = (props: TableViewRowProps[]) => {
console.log('type of TableViewContentList props is: ', typeof(props), props)
const tableViewContents = props.map((row) => console.log('row'))
return (
<div id="tableview-content-list">
{tableViewContents}
</div>
)
}
so when I print it here it's an object not an array anymore and it breaks at the .map. Can someone help me out please? I feel like I'm missing something minor.
Spread syntax (...) will give you an object when you apply it on an array inside an object.
type TableViewRowProps = number;
interface TableViewContainerProps {
content: TableViewRowProps[]
}
const props = {content: [1,2,3]}
const b = {...props.content}
console.log(b) // { "0": 1, "1": 2, "2": 3 }.
So the TableViewContentList get the props: props[0], props[1] and props[2], it's wrong.
All properties passed into a component will be attached in props, that's why you got an object. props will always be an object. So you'd better pass the content like this:
<TableViewContentList {...props} />
Or this:
<TableViewContentList content={props.content} />
Then you can map it to row:
export default function TableViewContentList(props: { content: TableViewRowProps[] }) {
const tableViewContents = props.content.map((row) => console.log('row'));
return <div id="tableview-content-list">{tableViewContents}</div>;
}

React - Iterating over State to dynamically generic element

This is a pretty basic question but I've been struggling; Likely due to my 'not quite understanding' how .map() works, and when to make an Object an array etc.
import React, { Component } from 'react';
import Post from './Post/Post';
class Posts extends Component {
state= {
posts: [
{
title: "Test",
content: "Some content"
},
{
title: "Test2",
content: "Some Additional Content"
}
]
};
render() {
let post = null;
post = Object.keys(this.state.posts).map(function (item, key) {
return <Post title={this.state.posts[item].title} content={this.state.posts[item].content} />
})
return (
<div>
<div>List of Posts</div>
{post}
</div>
);
}
}
export default Posts;
This is where I settled, since I got my JSBin version to work. However I'm getting a warning on my 'anonymous callback' function that creates the <Post> elements. <Post> is just expecting those two props and rendering them. I'm getting a warning on line 26 that mentions state being undefined. Am I not initializing state properly?
post = this.state.posts.map(function (item, key) {
return <Post title={item.title} content={item.content} key={key}/>
})
Your state.posts is an array and you should map over it.
post = Object.keys(this.state.posts).map((item, key) => {
return (
<Post
title={this.state.posts[item].title}
content={this.state.posts[item].content}
/>
)
})
as Subin says, what you need to do in that case is just map over the posts array.
But, to fix your code is very simple. just use arrow function instead of a regular function.
Do it like this:
post = this.state.posts.map(post => {
return <Post title={post.title} content={post.content} key={post.title}/>
})
When you map over array, you dont need to acces that state again, because post will be assigned to post inside map function. And then just acces title and content. and dont forget to pass key,there i've passed key={post.title} but that is not a good solution, because it needs to be unique.

Resources