Home > front end >  TypeError: Cannot read properties of undefined React js
TypeError: Cannot read properties of undefined React js

Time:01-22

I'm trying to output data from an array one per page.

This code throws an error Uncaught TypeError: Cannot read properties of undefined (reading 'word')

Although both the array newArr and the data newArr[i].word are output to the console.

const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);

function Home({ data, num }) {
  const [newArr, setNewArr] = useState([]);
  const [i, setI] = useState(0);

  useEffect(() => {
    setNewArr(shuffle(data));
  }, []);

  const randomCard = () => {
    if (i < newArr.length - 1) {
      setI(i   1);
      console.log(i);
      console.log(newArr);
      console.log(newArr[i].word);
    } else {
      setNewArr(shuffle(data));
      setI(0);
    }
  };
  return (
    <div>
      <div className="home-inner">
        {num >= 1 ? (
          <div className="home-stat">
            <div onClick={() => randomCard} className="home-random">
              <div className="hr-card">
                <p className="randomword">{newArr[i].word}</p>
                <p className="randomtranslate">{newArr[i].translate}</p>
              </div>
            </div>
          </div>
        ) : (
          <div>
            {" "}
              <Link className="main-btn" to="/addcard">
                Add 
              </Link>
          </div>
        )}
      </div>
    </div>
  );
}

export default Home;

CodePudding user response:

Your error is happening on the first render of your component. It is happening due to i is 0 and newArr is [] which is an empty array, and when you try to render it - newArr[0] - it returns undefined.

To fix that - initialize newArr with actual value and add an additional check at rendering place.

function Home({ data, num }) {
  // Initialize newArr with initial data instead of useEffect
  const [newArr, setNewArr] = useState(shuffle([...data]));
  const [i, setI] = useState(0);

  // just a utility
  const randomCard = useMemo(() => {
    // you can add some console.logs here for debugging purposes, just in case
    if (!newArr || i < 0 || i >= newArr.length) return undefined;
    return newArr[i];
  }, [newArr, i]);

  // changed naming here
  const randomizeCard = () => {
    if (i < newArr.length - 1) {
      setI(i   1);
    } else {
      setNewArr(shuffle([...data]));
      setI(0);
    }
  };

  return (
    <div>
      <div className="home-inner">
        {/* Added additinal check */}
        {num >= 1 && randomCard ? (
          <div className="home-stat">
            {/* fixed onClick binding */}
            <div onClick={randomizeCard} className="home-random">
              <div className="hr-card">
                <p className="randomword">{randomCard.word}</p>
                <p className="randomtranslate">{randomCard.translate}</p>
              </div>
            </div>
          </div>
        ) : (
          <div>
            {" "}
            <Link className="main-btn" to="/addcard">
              Add
            </Link>
          </div>
        )}
      </div>
    </div>
  );
}

Note: The sort() method sorts the elements of an array in place and returns the reference to the same array, so you are mutating data parameter which usually should not happen AND in same time React will not detect the changes if you setState with same "value", meaning Array object has the same reference all the time. Some details here: Difference between setting array equal to another one, or using three dots. Solution to that is shuffle([...data]) which will create a shallow copy of data.

Note2: calling setI(i 1); and console.log(i); just after it - will not work in the way you expect it due to state value will be updated on next render only

  • Related