1

I've got "TypeError: Cannot read properties of undefined (reading 'map')", even though useState() is initialized with array. The error occurs only in one component. Other components, which, I think, are also using useState and useEffect the same way, don't resolve with this error.

import { useState, useEffect } from "react/cjs/react.development";
import * as constants from "../../../../constants";

export default function Keywords(props) {
  const [movieKeywords, setMovieKeywords] = useState([]);

  useEffect(() => {
    const fetchKeywords = async () => {
      const data = await fetch(
        `${constants.TMDB_BASE_PATH}movie/${props.id}/keywords?api_key=${constants.API_KEY}`
      );

      const jsonData = await data.json();
      setMovieKeywords(jsonData.keywords);
      console.log("xdd");
    };

    fetchKeywords();
  }, []);
  return (
    <div className="flex flex-wrap">
      {movieKeywords.map((keyword) => {
        return (
          <div className="border font-oxygen m-1 rounded-xl cursor-pointer text-xs text-gray-300 px-2 py-1">
            <p>{keyword.name}</p>
          </div>
        );
      })}
    </div>
  );
}

I will be glad if anyone could point me in the right direction.

4
  • Please don't send an image and post the actual code. Commented Mar 4, 2022 at 13:51
  • Check out your jsonData in useEffect. Maybe its undefined. Commented Mar 4, 2022 at 13:53
  • 2
    jsonData.keywords may be undefined and therefore the line setMovieKeywords(jsonData.keywords) could set your movieKeywords state variable to undefined Commented Mar 4, 2022 at 13:54
  • jsonData.keywords is not undefined. The thing is that, when I add console.log and update the component without updating the whole DOM, everything works fine. console.log(data), console.log(jsonData), console.log(jsonData.keywords) all returns with responses. Commented Mar 4, 2022 at 14:06

2 Answers 2

3

You're probably just off by just a few ms in the timing of the API call and the rendering. A good practice is to check for the existence of the array you're mapping before trying to render any JSX. Set your initial state to null and do optional chaining on your map line.

Refactor your component something like this:

import { useState, useEffect } from "react/cjs/react.development";
import * as constants from "../../../../constants";

export default function Keywords(props) {
  const [movieKeywords, setMovieKeywords] = useState();

  useEffect(() => {
    const fetchKeywords = async () => {
      const data = await fetch(
        `${constants.TMDB_BASE_PATH}movie/${props.id}/keywords?api_key=${constants.API_KEY}`
      );

      const jsonData = await data.json();
      setMovieKeywords(jsonData.keywords);
      console.log("xdd");
    };

    fetchKeywords();
  }, []);
  return (
    <div className="flex flex-wrap">
      {movieKeywords?.map((keyword) => {
        return (
          <div className="border font-oxygen m-1 rounded-xl cursor-pointer text-xs text-gray-300 px-2 py-1">
            <p>{keyword.name}</p>
          </div>
        );
      })}
    </div>
  );
}

Notice the movieKeywords?.map, this will not execute map until movieKeywords is not null, meaning it will wait until the fetch resolves and your state is set.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, You are right, I was off by few ms, because the "id" prop I used in API call was a state in parent component and was initialized to string. Also thanks for letting me know about optional chaining, didn't knew that one ;).
id as a string shouldn't matter, so long as it is present. If you are getting id asynchronously in the parent component, meaning Keywords is rendered before props.id exists, that could also cause problems, but I can't know for sure without seeing the parent component as well.
what this feature called? I want to learn more about... obj?.method()
That is the optional chaining that I linked in my answer above.
2

"jsonData.keywords is not undefined." - The error is actually informing you that it is though. setMovieKeywords(jsonData.keywords); updates the state and then movieKeywords is undefined and unable to access a .map property/method.

From what I see, you are missing the props.id as a dependency for the useEffect and fetch. It sounds like props.id is initially not a valid value for the API request and you are getting an undefined keywords response.

You should only make the API request if you have all the required parameters, and your code should be robust enough to handle potentially invalid and bad responses.

  1. Add props.id to the useEffect hook's dependency array.
  2. Only make the fetch request if id is truthy.
  3. Handle potentially rejected Promises from fetch.
  4. Handle any potential bad state updates with undefined values.

Example:

import { useState, useEffect } from "react/cjs/react.development";
import * as constants from "../../../../constants";

export default function Keywords({ id }) {
  const [movieKeywords, setMovieKeywords] = useState([]);

  useEffect(() => {
    const fetchKeywords = async (id) => {
      try { // <-- (3) use try/catch to handle rejected Promise or other exceptions
        const data = await fetch(
          `${constants.TMDB_BASE_PATH}movie/${id}/keywords?api_key=${constants.API_KEY}`
        );

        const jsonData = await data.json();

        if (jsonData) { // <-- (4) only update state if defined response value
          setMovieKeywords(jsonData.keywords);
        }
      } catch(error) {
        // handle any errors, log, set error state, ignore(?), etc...
      }

      console.log("xdd");
    };

    if (id) { // <-- (2) only fetch if `id` is truthy
      fetchKeywords(id);
    }
  }, [id]); // <-- (1) add `id` as dependency

  return (
    <div className="flex flex-wrap">
      {movieKeywords?.map((keyword) => { // <-- Use Optional Chaining in case `movieKeywords` becomes falsey for any reason
        return (
          <div className="border font-oxygen m-1 rounded-xl cursor-pointer text-xs text-gray-300 px-2 py-1">
            <p>{keyword.name}</p>
          </div>
        );
      })}
    </div>
  );
}

1 Comment

You are absolutely right. Thank You!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.