The newArray i create and push url's to, is not added properly to my state variable (propFiles)
const [propFiles, setPropFiles] = useState([]);
useEffect(() => {
let newArray = [...propFiles];
data?.forEach((element) => {
element?.images?.forEach((el) => {
storageRef
.child(element.firebaseRef + "/" + el)
.getDownloadURL()
.then((url) => {
console.log(url) // this returns url's like "https://firebase/my-picture.jpg"
newArray.push(url);
console.log(newArray) // this updates the array as expected so the result of newArray is ok
setPropFiles(...propFiles, newArray) // This is where something goes wrong
});
});
});
}, [data]);
useEffect(() => {
console.log(propFiles); // The complete array values (of newArray) are not transfered proper to state. Only the first 2 url's are
}, [propFiles]);
So after each console.log you can find my information about what is going on.
As you can see, if i try to log propFiles, it only shows me the to 2 first elements of the foreach loop, not the complete newArray that was made.
Can someone please explain to me what would be the right approach to set state equal to the complete newArray that was made?
UPDATE To be extra clear about the outcomes of newArray and propFiles
This is what newArray looks like after the foreach:
[
0: "https://firebase/my-picture-1.jpg",
1: "https://firebase/my-picture-2.jpg",
2: "https://firebase/my-picture-3.jpg",
3: "https://firebase/my-picture-4.jpg",
4: "https://firebase/my-picture-5.jpg",
5: "https://firebase/my-picture-6.jpg",
6: "https://firebase/my-picture-7.jpg",
7: "https://firebase/my-picture-8.jpg",
8: "https://firebase/my-picture-9.jpg",
9: "https://firebase/my-picture-10.jpg",
10: "https://firebase/my-picture-11.jpg",
11: "https://firebase/my-picture-12.jpg",
]
So an array with 11 url (or strings) inside of it
This is what propFiles look like after it is set equal to newArray:
[
0: "https://firebase/my-picture-1.jpg",
1: "https://firebase/my-picture-2.jpg"
]
So an array with just 2 urls (or strings) inside of it.
How could this be possible?
Thanks in advance
propFiles state is an array and hence you are updating it, you should set an array back but you are setting comma separated values
Correct way to update it would be
setPropFiles(newArray)
since you have already used the propFiles while initialising the newArray
However, another thing to note is that you are looping over elements and updating state, it is always better to get the final updated array and update it only once. The way to do it would be to map over the items and use Promise.all
const [propFiles, setPropFiles] = useState([]);
useEffect(() => {
const promises = data?.map((element) => {
return element?.images?.map((el) => {
return storageRef
.child(element.firebaseRef + "/" + el)
.getDownloadURL()
.then((url) => {
console.log(url) // this returns url's like "https://firebase/my-picture.jpg"
console.log(newArray) // this updates the array as expected so the result of newArray is ok
return url
});
});
}).filter(Boolean); // remove all undefined values
// flatten the promise array since we have nested maps
const promisesArr = promises?.flat();
promisesArr && Promise.all(promisesArr).then(newArray => {
setPropFiles(prevPropsFiles => [...prevPropsFiles, ...newArray])
})
}, [data]);
Related
I've done what I believe produces an array of IDs of categories, and this is my code to try and use their IDs to return the keys of their children channels.
var filtered_category_ids = [""];
var filtered_category_ids = filtered_category_ids.reduce((acc, id) =>
acc.push(filtered_category_names.findKey((c) => c.name === name)));
var filtered_channel_ids = [];
const children = this.children;
filtered_category_ids.forEach(element => filtered_channel_ids.push((children.keyArray())));
console.log(filtered_channel_ids);
However, on running it, I get the TypeError "filtered_category_ids.forEach is not a function"
The second argument of Array.prototype.reduce is very important. It's the value that acc will take on at the beginning. If there is no second argument, it will take on the value of the first element in the array.
console.log([1, 2, 3].reduce((acc, curr) => acc + curr)) // acc starts as `1`
console.log([1, 2, 3].reduce((acc, curr) => acc + curr, 10)) // but what if we wanted it to start at `10`
This parameter is basically required when working with arrays, objects, etc.
console.log([1, 2, 3].reduce((acc, curr) => acc.push(curr))) // right now, `acc` is `1` (not an array; does not have the .push() method)
console.log([1, 2, 3].reduce((acc, curr) => acc.push(curr), [])); // start with an empty array
However, there is a second problem (as you can see from the above snippet). Array.prototype.push() actually returns the array's length, not the array itself.
console.log(
[1, 2, 3].reduce((acc, curr) => {
acc.push(curr); // push element to array
return acc; // but return the arr itself
}, [])
);
// second (simplified) method
console.log([1, 2, 3].reduce((acc, curr) => [...acc, curr], []))
You can also use Array.from(), which would be simpler in this situation.
console.log(Array.from([1, 2, 3], (num) => num);
There are a few other wonky things in your code, so here's what I suggest you do:
var filtered_category_names = ['']; // I think you might've meant `category_names` instead of `ids`
// you're trying to get IDs from names, so I'm not sure why you're reducing the ID array (before it's established)
// and using id as a parameter name when you use `name` in the actual function
var filtered_category_ids = Array.from(filtered_category_names, (name) =>
client.channels.cache.findKey((c) => c.name === name)
);
var filtered_channel_ids = [];
filtered_category_ids.forEach((id) =>
// filtered_channel_ids.push(children.keyArray()) // you're adding the same value to the array multiple times?
// get the category's children and add their IDs to the array
filtered.channel.ids.push(client.channels.cache.get(id).children.keyArray())
);
console.log(filtered_channel_ids);
I have two different arrays, one that has all the data that i'm using on a list and the another one that just has values from a filter that i allow the user to choose. So my first array is this one:
messages=[
{id:1, description:'abc', status:'SENT'},
{id:2, description:'hello', status:'CANCELED'},
{id:1, description:'bye', status:'SENT'}];
And my second array is this one:
items = ["SENT", "CLOSED", "RECEIVED"];
I have this hook to set the data also, that right now has all of it:
const [messageData, setMessageData] = useState([]);
And what i'm trying to do is to set on my hook the objects of the first array that have as a status the same value of one of the items on my second array (items) so i can later do a map of messageData on my render, in this case i have to render the first and third record of my messages array because they have a status of 'SENT' and that value is on my items array.
The thing is, i don't know how to compare and get those results, i tried doing a map of the message's array and filtering the items that had as a state one os the items array values, like this:
let search = [];
messages
.filter((option) => {
return(
option.value.status = items.value
)
})
.map((option) => {
search.push(option);
})
setMessageData(search);
But this is not working and im not sure how to solve it, can anyone help me with this?
This is a good situation for reduce()
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in single output value.
Array.prototype.reduce()
Calling reduce() on the items array iterates over each element and accumulates the results of the logic passed.
In the example below the accumulator acc is initialized as an empty array by passing [] as the second argument in the reduce() call.
const messages=[
{id:1, description:'abc', status:'SENT'},
{id:2, description:'hello', status:'CANCELED'},
{id:1, description:'bye', status:'SENT'}];
const items = ["SENT", "CLOSED", "RECEIVED"];
// iterate over each 'status' in the items array
let search = items.reduce((acc, status) => {
// retrieve objects from the messages array that match
const matches = messages.filter(message => message.status === status);
if (matches.length > 0) {
// if matches were found concat the returned array with the accumulator
acc = acc.concat(matches);
}
// return the accumulator to be used in the next iteration
return acc;
}, []);
console.log(search)
Turn it into a function
In response to your comment about receiving differing results I've turned it into a function to easily test results from different statusArrays and the reduce() call is working as expected.
const messages=[
{id:1, description:'abc', status:'SENT'},
{id:2, description:'hello', status:'CANCELED'},
{id:1, description:'bye', status:'RECEIVED'},
{id:1, description:'xyz', status:'SENT'},
{id:2, description:'hi', status:'SENT'},
{id:1, description:'bye again', status:'RECEIVED'}];
function filterMessagesByStatus(statusArray) {
if (!Array.isArray(statusArray)) return;
return statusArray.reduce((acc, status) => {
const matches = messages.filter(message => message.status === status);
if (matches.length > 0) {
acc = acc.concat(matches);
}
return acc;
}, []);
}
console.log(filterMessagesByStatus(["SENT", "CANCELED", "RECEIVED"]));
console.log(filterMessagesByStatus(["SENT"]));
console.log(filterMessagesByStatus(["CANCELED", "RECEIVED"]));
console.log(filterMessagesByStatus(["SENT", "CANCELED", "RECEIVED"]));
So I'm working with states in React, and i want to compare the two arrays/state and get the data from the first array that doesn't match with the data on the second array.
The first array is a state, and the second one is an array.
My first state/array looks like this = state : [ { id : id , productNumber : "productnumber"}... ] and the second array loos like this = array ["productnumber","productnumber"...],
and here what is my code's look like.
let newArray = [];
state.forEach(product=>{
array.forEach(productNumber=>{
if(product.productNumber !== productNumber){
newArray = [...newArray,product.productNumber];
}
})
})
and it doesn't work, it stores all the data from the first array/state and even doubled it. I tried using nested if statements, still doesn't. I don't know what todo.
let newArray = [];
state.forEach(({productNumber}) => {
if (array.indexOf(productNumber) === -1) {
newArray = [...newArray, productNumber];
}
})
Or similarly, you can filter the state array and return the products with productNumber not in your second array.
const newArray = state.filter(({productNumber}) => array.indexOf(productNumber) === -1);
I am trying to build my app so it does not read all 200+ documents on load but a handful at load and grab the rest as they scroll down.
I have working code that allows me to grab a page of results, say 2, then hit next and grab the next 2 but I want the next 2 to be added to the last so the user can scroll back up.
I am trying to push in my mutation but it's creating a nested mess. It returns 3 with the last having 2.
Mutations
setAddons(state, payload) {
state.addons = payload;
},
nextAddons(state, payload) {
state.addons.push(payload);
},
My 2 actions. One loads up with the page to grab first set. The second right now when I hit a button, but hopefully linked to scroll event once I get this sorted.
async firstAddons({ commit }) {
let addonsData = [];
return await db
.collection('addons')
.where('publish', '==', true)
.orderBy('timeUpdated', 'desc')
.limit(10)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
addonsData.push({
...doc.data(),
id: doc.id,
timeUpdated: doc.data().timeUpdated.toDate(),
timeCreated: doc.data().timeCreated.toDate()
});
let last = querySnapshot.docs[querySnapshot.docs.length - 1];
commit('setLast', last);
});
commit('setAddons', addonsData);
});
},
async nextAddons({ commit, state }) {
let addonsData = [];
await db
.collection('addons')
.where('publish', '==', true)
.orderBy('timeUpdated', 'desc')
.startAfter(state.last.data().timeUpdated)
.limit(10)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
addonsData.push({
...doc.data(),
id: doc.id,
timeUpdated: doc.data().timeUpdated.toDate(),
timeCreated: doc.data().timeCreated.toDate()
});
let next = querySnapshot.docs[querySnapshot.docs.length - 1];
commit('setLast', next);
});
commit('nextAddons', addonsData);
console.log(state.addons);
});
},
What is coming back
(3) [{…}, {…}, Array(2), __ob__: Observer]
0: {…}
1: {…}
2: Array(2)
0: {…}
1: {…}
length: 2
length: 3
Expected results are that the next page of results should be pushed onto the end of the last results and carry on.
Actual results: The array is putting each set into a nested array.
How can I put these together in one continuous array to use in my template?
Thank you
Solved and feel like a dope.
Turns out it was as simple as using spread.
setAddons(state, payload) {
state.addons = payload;
},
nextAddons(state, payload) {
state.addons.push(...payload);
},
That is working!
I have two arrays that I want to merge together to one array of objects...
The first array is of dates (strings):
let metrodates = [
"2008-01",
"2008-02",
"2008-03",..ect
];
The second array is of numbers:
let figures = [
0,
0.555,
0.293,..ect
]
I want to merge them to make an object like this (so the array items match up by their similar index):
let metrodata = [
{data: 0, date: "2008-01"},
{data: 0.555, date: "2008-02"},
{data: 0.293, date: "2008-03"},..ect
];
So far I do this like so: I create an empty array and then loop through one of the first two arrays to get the index number (the first two arrays are the same length)... But is there an easier way (in ES6)?
let metrodata = [];
for(let index in metrodates){
metrodata.push({data: figures[index], date: metrodates[index]});
}
The easiest way is probably to use map and the index provided to the callback
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = metrodates.map((date,i) => ({date, data: figures[i]}));
console.log(output);
Another option is to make a generic zip function which collates your two input arrays into a single array. This is usually called a "zip" because it interlaces the inputs like teeth on a zipper.
const zip = ([x,...xs], [y,...ys]) => {
if (x === undefined || y === undefined)
return [];
else
return [[x,y], ...zip(xs, ys)];
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = zip(metrodates, figures).map(([date, data]) => ({date, data}));
console.log(output);
Another option is to make a generic map function which accepts more than one source array. The mapping function will receive one value from each source list. See Racket's map procedure for more examples of its use.
This answer might seem the most complicated but it is also the most versatile because it accepts any number of source array inputs.
const isEmpty = xs => xs.length === 0;
const head = ([x,...xs]) => x;
const tail = ([x,...xs]) => xs;
const map = (f, ...xxs) => {
let loop = (acc, xxs) => {
if (xxs.some(isEmpty))
return acc;
else
return loop([...acc, f(...xxs.map(head))], xxs.map(tail));
};
return loop([], xxs);
}
let metrodates = [
"2008-01",
"2008-02",
"2008-03"
];
let figures = [
0,
0.555,
0.293
];
let output = map(
(date, data) => ({date, data}),
metrodates,
figures
);
console.log(output);
If you use lodash, you can use zipWith + ES6 shorthand propery names + ES6 Arrow functions for a one-liner, otherwise see #noami's answer.
const metrodata = _.zipWith(figures, metrodates, (data, date)=> ({ data, date }));