Getting error of fetched items after refreshing the page - javascript

I am fetching my data from external API as usual and this is the typical way I do it:
Fetch API:
const [tshirts, setTshirts] = useState([]);
const fetchData = () => {
fetch('apiEndpoint')
.then((response) => response.json())
.then((data) => {
setTshirts(data[0].clothes.regular.top); // path to my array
})
.catch((error) => {
console.log(error);
});
};
React.useEffect(() => {
fetchData();
}, []);
Map through an array:
const tshirtArray = tshirts.tShirt; // specifying the path
const listItems = tshirtArray.map((item) => <li>{item}</li>);
<ul>{listItems}</ul>
Example of data structure:
[
{
id: 1,
clothes: {
regular: {
top: {
sleeveless: [],
tShirt: [
"image-path-here"
],
.....
.....
.....
When I first time execute the code it works, but after some time or after refreshing the page I get an error of TypeError: Cannot read properties of undefined (reading 'map')
Why is that undefined? The path is correct and fetching the array should be as well. Can not find the reason of it not working.

I don't have reputation to comment, so let me try to clarify it for you through an answer. As #sojin mentioned, you cannot use tshirts.Tshirt since your state is of array type and arrays can't be used like objects, meaning that if there was an object of lets say exampleObject = { type: "shirt", color: "white } you could call it with exampleObject.type. Since you have an array of objects in your state (top that you are saving to state is still object which contains tShirt array), you first have to use index (to tell which object you want to use from the state array) and then you can use it like you wanted. For example, in your example there are 1 objects in state array. Array indexes start at 0. So you could do tshirts[0].tShirt to get the tShirt array from that object.
However, I would edit your code a bit. Instead of using tshirtArray constant, just do listItems from your state:
const listItems = tshirts.map((item) => {item.tShirt[0]});
Note: I've just used index 0 here to demonstrate the finding of the first item in tShirt array. If you want to see all tShirt image paths, then you may need to do nested mapping or other similar solutions.

Related

How to combine the received data in a state?

When I execute an API request, I get a lot of objects that are not connected by a common array. I'm trying to combine them into one state, but only one value is stored. How can I store all the received objects in the general array of my state? Everything would be fine, but I need the array to be stored in the state, in which all objects from the API will lie, in order to transfer this array to Redax
const [planets, setPlanets] = useState([]);
useEffect(() => {
(async () => {
const users = await axios(world);
const worldData = users.data;
setPlanets(worldData);
})();
}, [nextTeam, world]);
and i take get data
I am trying to transfer all these objects to state planets, but objects are transferred there individually. Not creating an array.
This is a screenshot of console.log(planets)
You can use Object.keys
let planets = {
tatooine:{
population: "1,3M", type: "desert"
},
coruscant:{
population: "130B", type: "city"
},
}
let array = []
Object.keys(planets).forEach(planet => array.push({
name: planet,
...planets[planet]
}))
console.log(array)

Zustand: change value of parameter in stored object

I am trying to create a function for a state of rated movies in Zustand.
The state consists of an array of objects, example with two entries:
ratedMovies: [
{ imdbID: "tt0076759", userRating: "5" },
{ imdbID: "tt0080684", userRating: "10" },
]
Below is the function managing ratedMovies changes. Here is where the issue lies. I want it to check whether an object with the same imdbID is present in ratedMovies state. And if so to update the value of it, instead of adding another object with the same imdbID but a new value.
If I try to change the rating of one of the movies from the above state example (with them in the state ofc), I get the IF console check and the app crashes with the error:
TypeError: Cannot create property 'userRating' on number '0'
If the state is empty or I change the rating of other movies, I get the ELSE console check, but they are still not added into the state.
addUserRating: (rating) => {
console.log("rating object", rating)
set((state) => {
if (state.ratedMovies.find((movie) => movie.imdbID === rating.imdbID)) {
console.log("add rating IF")
let index = state.ratedMovies.findIndex(
(movie) => movie.imdbID === rating.imdbID
)
index.userRating = rating.userRating
return [index, ...state.ratedMovies]
} else {
console.log("add rating ELSE")
return [rating, ...state.ratedMovies]
}
})
}
the onChange function on the input where one can rate a movie creates an identical object as in the state array and passes it to the function managing the state of ratedMovies:
const changeUserMovieRating = (event) => {
const movieRating = {
imdbID: modalDetails.imdbID,
userRating: event.target.value,
}
console.log(movieRating)
addUserRating(movieRating)
}
Output of which is:
{imdbID: 'tt0120915', userRating: '2'}
I hope i explained everything clearly, and I will highly appreciate any tips on how to solve this issue, thanks in advance!
Sorry but this whole apprach I had at the time of asking this question had no sense and had to be reworked completely.
I decided not to add the parameter during the fetch, as in another component the same data could be fetched. So I decided to instead keep the value of the 'userRating' in the local storage and if the fetched movie was already once rated by the 'user', the value would be displayed.

Why am I unable to map this array of objects when it is loaded in useState()?

learning React but I'm trying to map an array of objects in a dropdown box.
I'm getting a "Uncaught TypeError: ids.map is not a function".
Why am I getting this error when I have set the loadedIds I get from the GET request into useState's setIds?
https://jsfiddle.net/4jh9c6dv/53/
Thank you for helping a beginner :')
function Dropdown ()
{
const [ids, setIds] = React.useState([]);
React.useEffect(() => {
request.get(endpointIds).then((response) => {
setIds(response.data);
const loadedIds = [];
for (const id in response)
{
loadedIds.push({
id: ids,
});
}
setIds(loadedIds);
});
}, []);
const idsList = ids.map((id) =>
(
<option>id</option>
));
You do not need to use setIds(response.data);. Remove that.
response.data is not an array, response.data.ids is. That is why you get the error.
EDIT : Use for of instead of for in.
for of is used to iterate over items of an array/iterable object.
for in is used to iterate over index.
response.data should be response.data.id
you also forgot curly braces {id}

Why can't I use dot notation on React State?

I'm creating a flashcard app and I'm currently trying to set the front side of the flashcard to some text from an API.
My state:
const [deckWithCards, setDeckWithCards] = useState([]);
deckWithCards is a deck of flashcards and it looks like:
{name: 'Test Name', description: 'Test Description', id: 3, cards: Array(4)};
When I do deckWithCards.cards I get:
[{id: 1, front: 'Front of card', back: 'Back of Card', deckId: 1}]
If I was to have 4 cards in a deck, I'll get an array with 4 of these objects with the corresponding data.
I need access to all of this information however, when I try to do deckWithCards.cards.front, I get "Cannot read property 'front' of undefined."
I also tried looping through the cards array like:
let arr = [];
let allCards = deckWithCards.cards;
for (let i = 0; i < allCards.length; i++) {
arr.push(allCards.front);
}
This gave me: "Cannot read property 'length' of undefined."
How do I gain access to the items in this cards array?
Helper functions:
export async function readDeck(deckId, signal) {
const url = `${API_BASE_URL}/decks/${deckId}?_embed=cards`;
return await fetchJson(url, { signal });
}
export async function listCards(deckId, signal) {
const url = `${API_BASE_URL}/cards?deckId=${deckId}`;
return await fetchJson(url, { signal });
}
How I set my State:
useEffect(() => {
const abortController = new AbortController();
readDeck(deckId, abortController.signal)
.then(setDeckWithCards)
.catch(setError)
listCards(deckId, abortController.signal)
.then(setCards)
.catch(error)
return () => abortController.abort();
}, []);
There is a moment in time while your useEffect and your fetch are still running before you set the cards. During that time, the value of deckWithCards is going to be the initial value that you provided in your useState. Your component has to be built in a way where it can run properly and render properly with that initial value. If the eventual value of the resolved deck is an object, then it makes no sense that your initial value is an empty array.
const [deckWithCards, setDeckWithCards] = useState([]);
I recommend that you set the initial state to null or undefined. Before you access any properties on deckWithCards, you have to check that it has been set to an actual value.
const [deckWithCards, setDeckWithCards] = useState(null);
const allCards = deckWithCards ? deckWithCards.cards : [];
Here we check if deckWithCards is truthy (not null). If we have a deck, then we access the cards from the deck. If it's still null, we use an empty array. Either way, allCards will always be an array that you can map, loop through, etc.
const fronts = allCards.map( card => card.front );
return (
<ul>
{allCards.map( (card) => (
<div className="card" key={card.id}>
{card.front}
</div>
))}
</ul>
)
First of all, the first state is []
const [deckWithCards, setDeckWithCards] = useState([]);
So in the very first run I except that deckWithCards is an array with no property cards. This can trigger the error you're encountering.
Otherwise, if deckWithCards is an array of objects, then to access the cards of, let's say, first object, you need to enter deckWithCards[0].cards
If instead you are correctly setting the value of deckWithCards with an object as you described above, deckWithCards.cards should correctly return the excepted list of cards.

Appending multi dimensional array in react state

I'm trying to append array which is react state:
const [ products, setProducts ] = useState([])
useEffect(() => {
config.categories.forEach(category => {
service.getCategory(category.name).then(data => {
const copy = JSON.parse(JSON.stringify(products))
copy[category.id] = data
setProducts(copy)
})
})
},[])
service.getCategory() fetches data over HTTP returning array. products is nested array, or at least it's suppose to be. config.category is defined as:
categories: [
{
name: 'product1',
id: 0
},
{
name: 'product2',
id: 1
},
{
name: 'product3',
id: 2
}]
}
Eventually products should be appended 3 times and it should contain 3 arrays containing products from these categories. Instead products array ends up including only data from last HTTP fetch, meaning the final array looks something like this
products = [null, null, [{},{},{},..{}]].
I hope someone knows what's going on? Been tinkering with this for a while now.
The problem is that your fulfillment handlers close over a stale copy of products (the empty array that's part of the initial state). In a useEffect (or useCallback or useMemo, etc.) hook, you can't use any state items that aren't part of the dependency array that you provide to the hook. In your case, you just want to get the data on mount, so an empty dependency array is correct. That means you can't use any state items in the callback.
What you can do instead is use the callback form of the state setter:
const [ products, setProducts ] = useState([]);
useEffect(() => {
config.categories.forEach(category => {
service.getCategory(category.name).then(data => {
setProducts(products => { // Use the callback form
const copy = products.slice(); // Shallow copy of array
copy[category.id] = data; // Set this data
return copy; // Return the shallow copy
});
});
});
}, []);
Or more concisely (but harder to debug!) without the explanatory comments:
const [ products, setProducts ] = useState([]);
useEffect(() => {
config.categories.forEach(category => {
service.getCategory(category.name).then(data => {
setProducts(products => Object.assign([], products, {[category.id]: data}));
});
});
}, []);
Those both use the same logic as your original code, but update the array correctly. (They also only make a shallow copy of the array. There's no need for a deep copy, we're not modifying any of the objects, just the array itself.)
But, that does a state update each time getCategory completes — so, three times in your example of three categories. If it happens that the request for id 2 completes before the request for id 1 or 0, your array will look like this after the first state update:
[undefined, undefined, {/*data for id = 2*/}]
You'll need to be sure that you handle those undefined entries when rendering your component.

Categories