Kaushik Kalesh
Kaushik Kalesh

Reputation: 43

React - TypeError: Cannot read property 'img' of undefined

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

Answers (1)

Drew Reese
Drew Reese

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

Demo

Edit react-typeerror-cannot-read-property-img-of-undefined

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

Related Questions