Reputation: 43
I am making a card game using React, and for some reason I am getting TypeError: Cannot read property 'img' of undefined
.
The Code causing the error:
import React, { useState } from "react";
import Card from "./components/Card";
const App = () => {
const [cards, setCards] = useState([
{
name: "King",
img: "https://image.freepik.com/free-vector/king-crown-esport-logo-design_177315-269.jpg",
pts: 10000,
},
{
name: "Minister",
img: "https://i.pinimg.com/474x/51/59/b3/5159b321713edef6c6a3b8f930f667a1.jpg",
pts: 5000,
},
{
name: "Cop",
img: "https://image.shutterstock.com/image-vector/police-officer-badge-icon-vector-260nw-613493573.jpg",
pts: 100,
},
{
name: "Thief",
img: "https://images1.livehindustan.com/uploadimage/library/2019/05/03/16_9/16_9_1/thief_symbolic_photo__1556892712.jpg",
pts: 0,
},
]);
const [shuffledCards, setShuffledCards] = useState([]);
for (let i = 0; i < 4; i++) {
const randCard = cards[Math.floor(Math.random() * (cards.length + 1))];
setShuffledCards([...shuffledCards, randCard]);
setCards(cards.filter((c) => c !== randCard));
}
return (
<div style={{ display: "flex", flexDirection: "row" }}>\
{shuffledCards.map((c) => (
<Card img={c.img} name={c.name} pts={c.pts} />
))}
</div>
);
};
export default App;
The link to the project (online Gitpod.io) - https://purple-salmon-uy3kza3n.ws-us10.gitpod.io/?folder=vscode-remote%3A%2F%2Fsapphire-quokka-1i5clgk7.ws-us09.gitpod.io%3A443%2Fworkspace (caution it might change, some weird gitpod thing)
Now, when I printed the shuffledCards
array in the for
loop, after some loops I noticed an undefined object was getting added to the array. I think that's the problem, and I don't know why or how it's happening, and also how to fix it.
Looking forward to hearing from you guys. Thank you in Advance.
Upvotes: 2
Views: 187
Reputation: 202585
The main issue is how you compute a random card, you are indexing out of the array.
const randCard = cards[Math.floor(Math.random() * (cards.length + 1))];
You want to use the length.
const randCard = cards[Math.floor(Math.random() * (cards.length))];
Another issue is that you are unconditionally updating state from the body of the component, this will lead to render looping.
Place the loop in an useEffect
hook and use a functional state update since you are enqueueing state updates in a loop.
useEffect(() => {
for (let i = 0; i < 4; i++) {
const randCard = cards[Math.floor(Math.random() * (cards.length))];
setShuffledCards((shuffledCards) => [...shuffledCards, randCard]);
setCards((cards) => cards.filter((c) => c !== randCard));
}
}, []);
It's redundant to store two arrays. I would look into and implement the fisher-yates shuffle.
Here's an example effect:
useEffect(() => {
setCards((cards) => {
// create copy
const shuffledCards = cards.slice();
// shuffle
for (let i = cards.length - 1; i >= 0; i--) {
const randIndex = Math.floor(Math.random() * i);
[shuffledCards[i], shuffledCards[randIndex]] = [
shuffledCards[randIndex],
shuffledCards[i]
];
}
// return next state
return shuffledCards;
});
}, [shuffledCards]);
In this demo I added a button to shuffle, and this updates the useEffect
dependency to trigger another shuffle. This effect dependency can be anything you need it to be triggered by though for your specific use-case.
demo code
const App = () => {
const [cards, setCards] = useState([
{
name: "King",
img:
"https://image.freepik.com/free-vector/king-crown-esport-logo-design_177315-269.jpg",
pts: 10000
},
{
name: "Minister",
img:
"https://i.pinimg.com/474x/51/59/b3/5159b321713edef6c6a3b8f930f667a1.jpg",
pts: 5000
},
{
name: "Cop",
img:
"https://image.shutterstock.com/image-vector/police-officer-badge-icon-vector-260nw-613493573.jpg",
pts: 100
},
{
name: "Thief",
img:
"https://images1.livehindustan.com/uploadimage/library/2019/05/03/16_9/16_9_1/thief_symbolic_photo__1556892712.jpg",
pts: 0
}
]);
const [shuffledCards, setShuffledCards] = useState(true);
const shuffle = () => setShuffledCards((t) => !t);
useEffect(() => {
setCards((cards) => {
const shuffledCards = cards.slice();
for (let i = cards.length - 1; i >= 0; i--) {
const randIndex = Math.floor(Math.random() * i);
[shuffledCards[i], shuffledCards[randIndex]] = [
shuffledCards[randIndex],
shuffledCards[i]
];
}
return shuffledCards;
});
}, [shuffledCards]);
return (
<>
<div style={{ display: "flex", flexDirection: "row" }}>
{cards.map((c) => (
<Card key={c.name} img={c.img} name={c.name} pts={c.pts} />
))}
</div>
<button type="button" onClick={shuffle}>
Shuffle Cards
</button>
</>
);
};
Upvotes: 5