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.
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.
"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.
props.id to the useEffect hook's dependency array.fetch request if id is truthy.fetch.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>
);
}