How to combine the received data in a state? - javascript

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)

Related

name an object in Array

I'm doing multiple fetchs with Promise.all. So I receive data like this :
[
0: {
...
},
1: {
...
}
]
But I would like to name my Objects. So I can do data.myObject istead of data[0].
I would like the index to be a string that I chose.
For example, i'd like to get :
[
"home": {
...
},
"product": {
...
}
]
Is is possible ? Thanks
No, this is not possible. Promise.all works on iterables (like arrays), not on objects. Name your values after the Promise.all call:
const data = await Promise.all([…, …]);
const home = data[0], product = data[1];
becomes with destructuring
const [home, product] = await Promise.all([…, …]);
Promise.all works with an array. Arrays deal in numerically indexed (ordered) values.
You'd need to have a name stored somewhere for each promise, and then generate the object from them once the promises had resolved.
e.g.
const names = ['home', 'product'];
const promises = [fetchHome(), fetchProduct()];
const results = await Promise.all(promises);
const resultsByName = names.reduce((prev, curr, index) => {
return {...prev, [curr]: results[index]};
}, {});
You could use a similar approach without the second array if the name was available in the resolved values of fetchHome() and fetchProduct().
You can destructure the array of Promise results in the Promise.all.
const [home, product, toolbar, nav] = await Promise.all([
getHome(), getProduct(), getToolBar(), getNav()
]);
Since the results are an array like anything else you can destructure arrays and even use the ...rest syntax:
const [home, product, toolbar, nav, ...otherPromises] = await Promise.all([
getHome(), getProduct(), getToolBar(), getNav(), getOtherThing1()
]);
// otherPromises will be an array that you'll have to access
// with numeric keys as before:
// eg. otherPromises[0] might be the first non-named promise
// the result of getOtherThing1()
Seems like you want to convert Array to an object so you can get data by calling a key.
const arr = [ { id: "home", val: "val1" }, { id: "product", val: "val2" }];
const convert = arr.reduce((a,b) => (a[b.id] ??= b,a),{});
console.log(convert);
console.log(convert.home);

Filtering data after fetching in React

I need to make a list of objects based on combined data from 2 arrays, one comes from a localStorage and the second one from Django backend. First of all objects from localStorage are displayed by showCart() function
export const showCart = () => {
if (typeof window !== undefined) {
if (localStorage.getItem("cart")) {
return JSON.parse(localStorage.getItem("cart"));
};
};
};
it returns data in this format: FE: { id: 1, amount: 7, size: "L", product: 1 }. product is the Foreign Key needed to match data from other array.
The second array comes form a backend and it is feched by getAllProducts() function
export const getAllProducts = () => {
return fetch(`${url}/products/`, {method: "GET"})
.then((response) => {
return response.json();
})
.catch((error) => console.log(error))
};
It returns data in this format: FE { name: "Red", id: 3, price: 33, image:"some-url"}
​​
Now I need to create another list of objects by merging then by product of an object in first array with id of an object from the second one. The objects in the third array need to contain amount and size from first array as well as name, price and image from the second one. In the end I want to store it in useState().
This is what I came up with, I guess my code stops working arter first for loop:
const [cart, setCart] = useState([]);
const CheckAnonymousCart = () => {
getAllProducts()
.then((data) => {
const localCart = showCart();
var products = [];
for (let i = 0; i < localCart.lenght; i++) {
for (let y = 0; y < data.lenght; y++) {
if (localCart[i].product === data[y].id) {
console.log(localCart[i].product, data[y].id)
const item = {
name: data[y].name,
price: data[y].price,
image: data[y].image,
amount: localCart[i].amount,
size: localCart[i].size,
}
products.push(item)
break;
}
}
}
setCart(products);
})
.catch((error) => console.log(error))
};
​​Any thoughts?
In addition to Jacob's comment, you probably want to avoid FETCH'ing all products from the DB, because it requires more DB resources, most of the info is not required, and it makes the for-loop take longer to JOIN both lists.
Ideally, you would use a parameterized query like so:
return fetch(`${url}/products/?id=1&id=2&id=3`, {method: "GET"})
Where ?id=1&id=2&id=3 are a subset of the product IDs that you're retrieving.
Note: You will also want to sanitize/validate the product IDs in localStorage, because the data can be modified by the end-user, which is a potential attack vector by malicious users.
The problem could simply be the typo from the for loop conditions, but you can also accomplish this more succinctly using the JS ES6 methods:
const products = localCart.map(item => {
const match = data.find(x => x.id === item.product);
return {
amount,
size,
name: match?.name,
price: match?.price,
image: match?.image
}
});

Getting error of fetched items after refreshing the page

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.

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