useState append object into an array of object - javascript

My object is create an array of object as follows [{},{},{}]
State variable constructor like this:
this.state:{...some states,
parsed:[{}]}
Every object come from get IPFS with async function:
IPFSREADER = element => {
ipfs.cat(convertedIPFSaddress).then(result => {
let cons = result.toString('utf8')
const consdata = JSON.parse(cons);
// NEED useState save consdata to parsed state
}
When I get data, I am pushing this data to state with useState, but I tried as follows way:
this.setState({parsed:consdata}) result: > Object:{some data...}
this.setState({...[parsed],parsed:[consdata]}) result: [0:Object:{some data...}]
When there is a new data coming from ipfs, this only changes data, where is into the array, but doesn't append to tail of previous data.
this.setState(parsed => [...data, parsed]); follow Joseph D.'s answer, but throw ReferenceError: can't access lexical declaration 'data' before initialization error. Try this solution changed above function as follows:
changed the function to async: IPFSREADER = async element => {..
added await in front of ipfs.cat: const { data } = await ipfs.cat(...
How can solve this problem? I tried many solutions, but can't change the result... Thanks.

It looks like you are trying to do setState({...state, parsed: [...state.parsed, consdata]})

Related

React State is Changing Without Using setState()

I'm developing a quiz/test website. I want to see different questions when I move to next question and don't want to see same answers at the same time.
I have a state array varaible which is calling allWords. This state
will keep all words.
And I have another state array variable which calls like
neverAskedWords. This state will keep words which never used
always.
I'm creating a new array variable and defining with allWords in a function. When I'm removing any record in the new array variable then that record is removing in allWords variable as well... Why?
I want to remove any record in that temporary array and want to save updated version to neverAskedWords state. In this way I could see different questions always. Here is my codes.
const [allWords, setAllWords] = useState([])
const [neverAskedWords, setNeverAskedWords] = useState([])
async function getAllData(){
axios
.get(`http://127.0.0.1:3000/api/improve-language`)
.then(res => {
setAllWords(res.data)//defining allWords
setNeverAskedWords(res.data)//defining neverAskedWords
firstQuestionAndAnswers(res.data)//sending all datas by parameter, bacause when I'm trying to get datas by using `allWords` state, it would be undefined. That's why sending all data by parameter for the first time to set first question and answers.
})
.catch(err =>{
console.log(err)
})
}
async function firstQuestionAndAnswers(wordsList){
let neverAskedList = await wordsList //creating and defining temporary variables
const allWordsList = await wordsList //creating and defining temporary variables
//some not necessary codes for this issue
const questionIndex = randomNumber(neverAskedList.length)
const firstQuestion = neverAskedList[questionIndex]
let firstAnswers = []
for (let i = 0; i < 4; i++) {
let answerIndex = randomNumber(allWordsList.length)
firstAnswers[i] = allWordsList[answerIndex]
allWordsList.splice(answerIndex, 1)//and here! I'm removing this record to prevent using it again next time, there will be different answers always
}
//some not necessary codes for this issue
firstAnswers.push(firstQuestion)
const randomisedAnswers = firstAnswers.sort(()=>Math.random() - 0.5)
//some not necessary codes for this issue
setQuestion(firstQuestion)
setAnswers(randomisedAnswers)
//and then here! I'm removing the used question in this time to prevent using it again, there will be different questions always and never see this question again
neverAskedList.splice(questionIndex, 1)
setNeverAskedWords(neverAskedList)
}
allWords should'nt change. But changing, because of why?
So the most obvious thing that I see in your code is that you are modifying the same object. What you should do instead is use the spread operator.
const [allWords, setAllWords] = useState([])
const [neverAskedWords, setNeverAskedWords] = useState([])
async function getAllData(){
axios
.get(`http://127.0.0.1:3000/api/improve-language`)
.then(res => {
setAllWords(res.data)//defining allWords
setNeverAskedWords(res.data)//defining neverAskedWords
firstQuestionAndAnswers(res.data)//sending all datas by parameter, bacause when I'm trying to get datas by using `allWords` state, it would be undefined. That's why sending all data by parameter for the first time to set first question and answers.
})
.catch(err =>{
console.log(err)
})
}
async function firstQuestionAndAnswers(wordsList){
// don't use await for js objects, should be used only with promises.
// use spread operator to make copy of the wordList array so you never actually modify the original object
let neverAskedList = [...wordsList]
const allWordsList = [...wordsList]
//some not necessary codes for this issue
const questionIndex = randomNumber(neverAskedList.length)
const firstQuestion = neverAskedList[questionIndex]
let firstAnswers = []
for (let i = 0; i < 4; i++) {
let answerIndex = randomNumber(allWordsList.length)
firstAnswers[i] = allWordsList[answerIndex]
allWordsList.splice(answerIndex, 1)//and here! I'm removing this record to prevent using it again next time, there will be different answers always
}
//some not necessary codes for this issue
firstAnswers.push(firstQuestion)
const randomisedAnswers = firstAnswers.sort(()=>Math.random() - 0.5)
//some not necessary codes for this issue
setQuestion(firstQuestion)
setAnswers(randomisedAnswers)
//and then here! I'm removing the used question in this time to prevent using it again, there will be different questions always and never see this question again
neverAskedList.splice(questionIndex, 1)
setNeverAskedWords(neverAskedList)
}
If you don't understand why it happened then here's a short explanation. In js when you do const a = { key: 'val' } you created a variable that references the memory block that is actually storing your object. And when you do const b = a you are creating another variable that references the same memory block. So updating 1 automatically changes the other one.

Access data object properties inside an object

So I have the following piece of code:
const Menus = wp.api.models.Post.extend({
url: wpApiSettings.root + 'fh/v1/menus/' + path,
});
const menus = new Menus();
console.log(menus);
menus outputs the following object:
How would I be able to access the data: { object and it's properties? It looks like it's an object inside an object.
When I do console.log(menus.attribute) that works fine, but console.log(menus.attributes.data) or even console.log(menus.attributes.success) just returns a undefined answer.
I tried doing the following:
console.log(menus.attributes['data'] also with a undefined answer.
All help is appreciated!
Try menus.attributes[0].data.
It may work. If not work, try to console this on chrome browser and share the screenshot
Probably you have an async problem here. You can try console.log the result inside a callback function.
For example you can try a similar approach like this :
import axios from 'axios';
async function getItems() {
const response = await axios.get(SOME_URL);
console.log('done', response);
return response;
}
getItems().then(items => console.log('items: ', items))
If anyone is ever wondering how to access the data, here is how I had this:
const Menus = wp.api.models.Post.extend({
url: wpApiSettings.root + 'fh/v1/menus/' + path,
});
const menus = new Menus();
console.log(menus) outputted an Object with properties that have other objects.
To access those properties inside the object, do the following:
menus.fetch().then(posts => {
const data = posts.data;
}
Now you're able to access your object properties inside the object - I hope this helps.

Firebase real time database access data array from deleted node

I am deleting a FRTDB node, I want to access deleted data from that node. the functions looks as follow:
exports.events = functions.database.ref('/events/{eventId}').onWrite(async (change, context) => {
const eventId = context.params.eventId
if (!change.after.exists() && change.before.exists()) {
//data removed
return Promise.all([admin.database().ref(`/events/${eventId}/dayofweek`).once('value')]).then(n => {
const pms = []
const days = n[0]
days.forEach(x => {
pms.push(admin.database().ref(`${change.before.val().active ? 'active' : 'inactive'}/${x.key}/${eventId}`).set(null))
})
return Promise.all(pms)
});
else {
return null;
}
})
The probem I am having is that
admin.database().ref(`/events/${eventId}/dayofweek
do not loop the data because it seems data is no longer there so the forEach is not working. How can I get access to this data and get to loop the deleted data?
Of course you won't be able to read data that was just deleted. The function runs after the delete is complete. If you want to get the data that was just deleted, you're supposed to use change.before as described in the documentation:
The Change object has a before property that lets you inspect what was
saved to Realtime Database before the event. The before property
returns a DataSnapshot where all methods (for example, val() and
exists()) refer to the previous value. You can read the new value
again by either using the original DataSnapshot or reading the after
property. This property on any Change is another DataSnapshot
representing the state of the data after the event happened.
The data that was deleted from the database is actually included in the call to your Cloud Function. You can get if from change.before.
exports.events = functions.database.ref('/events/{eventId}').onWrite(async (change, context) => {
const eventId = context.params.eventId
if (!change.after.exists() && change.before.exists()) {
//data removed
days = change.before.val().dayofweek;
...
})

Get Nested Docs in Firestore

Here is my data:
I want to iterate through each event_prod in event_prods and go to the eventGroups subcollection. Once in that sub-collection, I want to loop through each eventGroup in eventGroups and get doc data.
Here's my code thus far:
async function getAllEventGroups() {
let eventGroups = []
try {
let eventProducerRef = await db.collection('event_prods')
let allEventProducers = eventProducerRef.get().then(
producer => {
producer.forEach(doc => console.log(doc.collection('eventGroups'))
}
)
} catch (error) {
console.log(`get(): there be an error ${error}`)
return []
}
return eventGroups
}
Obviously, it doesn't do what I want, but I can't figure out how to get access to the eventGroups subcollection. Calling 'collection()' on 'doc' is undefined. Can someone please help fix this? By the way, I don't care if this requires two (or more) queries as long as I don't have to bring in data I will never use.
Edit: this is not a duplicated because I know the name of my subcollection
You call the .collection on the QueryDocumentSnapshot. This methods doesn't exist there. But as the QueryDocumentSnapshot extends DocumentSnapshot you can call ref on it to get the reference to the requested document.
```
let allEventProducers = eventProducerRef.get().then(
producer => {
producer.forEach(doc => console.log(doc.ref.collection('eventGroups')) // not the ref here
}
)
eventProducerRef is a CollectionReference. The get() method on that yields a QuerySnapshot, which you are storing in producer. When you iterate it with forEach(), you are getting a series of QueryDocumentSnapshot objects, which you're storing in doc. QueryDocumentSnapshot doesn't have a method called collection(), as you are trying to use right now.
If you want to reach into a subcollection of a document, build a DocumentReference to the document, then call its collection() method. You'll need to use the id of each document for this. Since a QueryDocumentSnapshot subclasses DocumentSnapshot, you can use its id property for this:
let eventProducerRef = await db.collection('event_prods')
let allEventProducers = eventProducerRef.get().then(
producer => {
producer.forEach(snapshot => {
const docRef = eventProducerRef.doc(snapshot.id)
const subcollection = docRef.collection('eventGroups')
})
}
)

How to collect and return aggregated data as an Array from a table in Protractor?

I am trying to aggregate a list of dates from a data table, written in Angular, in a Protractor test. I'm doing the aggregation from a PageObject class that is called in the Protractor test. I know that my code is successfully grabbing the text I want, but when I try to console.log the returned array, I get an empty array. I'm still new to Javascript/Typescript, Angular, and Protractor and this may be a result of my newness to the asynchronous nature of this development environment.
Code is as follows,
The PageObject SpecMapper class with method:
import { browser, element, by } from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
// currently this css selector gets rows in both import and export tables
// TODO: get better identifiers on the import and export tables and columns
element.all(by.css('md-card-content tbody tr.ng-tns-c3-0')).each(function(row, index){
// check outerHTML for presence of "unclickable", the rows in the export table
row.getAttribute('outerHTML').then(function(outerHTML:string) {
// specifically look for rows without unclickable
if(outerHTML.indexOf("unclickable") < 0){
// grab the columns and get the third column, where the date submitted field is
// TODO: get better identifiers on the import and export columns
row.all(by.css("td.ng-tns-c3-0")).get(2).getText().then(function(text:string) {
stringDatesArray.push(text);
});
}
});
});
return stringDatesArray;
}
}
I know it's not the prettiest code, but it's temporary place holder while my devs make me better attributes/classes/ids to grab my variables. Key things to note is that I create a string Array to hold the values I consider relevant to be returned when the method is finished.
I used WebStorm and put a breakpoint at the stringDatesArray.push(text) and return stringDatesArray lines. The first line shows that the text variable has a string variable that I'm looking for and is successfully getting pushed. I see the success in debug mode as I can see the stringDatesArray and see the values in it. The second line though, the array return, shows that the local variable stringDatesArray is empty. This is echoed in the following code when I try to console.log the array:
The Protractor run Spec class with my test in it:
import { SpecMapperPage } from "./app.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('verify sort order is desc', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
console.log(page.getImportDateSubmittedColumnValues());
});
});
});
});
I acknowledge that this code is not actively using the niceties of Protractor, there's a known issue with our app that will not be addressed for a couple of months, so I am accessing the driver directly 99% of the time.
You'll note that I call the method I posted above as the very last line in the browser.driver.sleep().then() clause, page.getImportDateSubmittedColumnValues().
I thought maybe I was running into asynchronous issues with the call being done before the page was loaded, thus I put it in the .then() clause; but learned with debugging that was not the case. This code should work once I have the array returning properly though.
The console.log is printing an empty [] array. That is synonymous with the results I saw when debugging the above method directly in the PageObject SpecMapper class. I wish to do some verification that the strings are returned properly formatted, and then I'm going to do some date order comparisons. I feel like returning an array of data retrieved from a page is not an unusual request, but I can't seem to find a good way to Google what I'm trying to do.
My apologies if I am hitting some very obvious roadblock, I'm still learning the nuances of Typescript/Angular/Protractor. Thank you for your consideration!
My attempted to used collated promises seemed promising, but fell through on execution.
My Updated PageObject SpecMapper Class
import {browser, element, by, protractor} from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let promisesArray = [];
let stringDatesArray: Array<string> = [];
// This CSS selector grabs the import table and any cells with the label .created-date
element.all(by.css('.import-component .created-date')).each(function(cell, index) {
// cell.getText().then(function(text:string) {
// console.log(text);
// });
promisesArray.push(cell.getText());
});
return protractor.promise.all(promisesArray).then(function(results) {
for(let result of results) {
stringDatesArray.push(result);
}
return stringDatesArray;
});
}
}
My Updated Spec test Using The Updated SpecMapper PO Class
import { SpecMapperPage } from "./specMapper.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('TC2963: ImportComponentGrid_ShouldDefaultSortBySubmittedDateInDescendingOrder_WhenPageIsLoaded', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
page.getImportDateSubmittedColumnValues().then(function(results) {
for(let value of results) {
console.log("a value is: " + value);
}
});
});
});
});
});
When I breakpoint in the PO class at the return stringDatesArray; line, I have the following variables in my differing scopes. Note that the promisesArray has 3 objects, but the results array going into the protractor.promise.all( block has 0 objects. I'm not sure what my disconnect is. :/
I think I'm running into a scopes problem that I am having issues understanding. You'll note the commented out promise resolution on the getText(), and this was my POC proving that I am getting the string values I'm expecting, so I'm not sure why it's not working in the Promise Array structure presented as a solution below.
Only other related question that I could find has to do with grabbing a particular row of a table, not specifically aggregating the data to be returned for test verification in Protractor. You can find it here if you're interested.
As you've alluded to your issue is caused by the console.log returning the value of the variable before its actually been populated.
I've taken a snippet from this answer which should allow you to solve it: Is there a way to resolve multiple promises with Protractor?
var x = element(by.id('x')).sendKeys('xxx');
var y = element(by.id('y')).sendKeys('yyy');
var z = element(by.id('z')).sendKeys('zzz');
myFun(x,y,z);
//isEnabled() is contained in the expect() function, so it'll wait for
// myFun() promise to be fulfilled
expect(element(by.id('myButton')).isEnabled()).toBe(true);
// in a common function library
function myFun(Xel,Yel,Zel) {
return protractor.promise.all([Xel,Yel,Zel]).then(function(results){
var xText = results[0];
var yText = results[1];
var zText = results[2];
});
}
So in your code it would be something like
getImportDateSubmittedColumnValues() {
let promisesArray = [];
let stringDatesArray: Array<string> = [];
// currently this css selector gets rows in both import and export tables
// TODO: get better identifiers on the import and export tables and columns
element.all(by.css('md-card-content tbody tr.ng-tns-c3-0')).each(function(row, index){
// check outerHTML for presence of "unclickable", the rows in the export table
row.getAttribute('outerHTML').then(function(outerHTML:string) {
// specifically look for rows without unclickable
if(outerHTML.indexOf("unclickable") < 0){
// grab the columns and get the third column, where the date submitted field is
// TODO: get better identifiers on the import and export columns
promisesArray.push(row.all(by.css("td.ng-tns-c3-0")).get(2).getText());
}
});
});
return protractor.promise.all(promisesArray).then(function(results){
// In here you'll have access to the results
});
}
Theres quite a few different ways you could do it. You could process the data in that method at the end or I think you could return the array within that "then", and access it like so:
page.getImportDateSubmittedColumnValues().then((res) =>{
//And then here you will have access to the array
})
I don't do the Typescript but if you're just looking to get an array of locator texts back from your method, something resembling this should work...
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
$$('.import-component .created-date').each((cell, index) => {
cell.getText().then(text => {
stringDatesArray.push(text);
});
}).then(() => {
return stringDatesArray;
});
}
The answer ended up related to the answer posted on How do I return the response from an asynchronous call?
The final PageObject class function:
import {browser, element, by, protractor} from 'protractor';
export class SpecMapperPage {
getImportDateSubmittedColumnValues() {
let stringDatesArray: Array<string> = [];
let promisesArray = [];
// return a promise promising that stringDatesArray will have an array of dates
return new Promise((resolve, reject) => {
// This CSS selector grabs the import table and any cells with the label .created-date
element.all(by.css('.import-component .created-date')).map((cell) => {
// Gather all the getText's we want the text from
promisesArray.push(cell.getText());
}).then(() => {
protractor.promise.all(promisesArray).then((results) => {
// Resolve the getText's values and shove into array we want to return
for(let result of results) {
stringDatesArray.push(result);
}
}).then(() => {
// Set the filled array as the resolution to the returned promise
resolve(stringDatesArray);
});
});
});
}
}
The final test class:
import { SpecMapperPage } from "./specMapper.po";
import {browser, ExpectedConditions} from "protractor";
describe('spec mapper app', () => {
let page: SpecMapperPage;
let PROJECT_ID: string = '57';
let PROJECT_NAME: string = 'DO NOT DELETE - AUTOMATED TESTING PROJECT';
beforeEach(() => {
page = new SpecMapperPage();
});
describe('import/export page', () => {
it('TC2963: ImportComponentGrid_ShouldDefaultSortBySubmittedDateInDescendingOrder_WhenPageIsLoaded', () => {
browser.waitForAngularEnabled(false);
// Step 1: Launch Map Data from Dashboard
page.navigateTo(PROJECT_ID);
browser.driver.sleep(5000).then(() => {
// Verify: Mapping Screen displays
// Verify on the specmapper page by checking the breadcrumbs
expect(page.getProjectNameBreadCrumbText()).toContain(PROJECT_NAME);
expect(page.getProjectMapperBreadCrumbText()).toEqual("MAPPER");
// Verify: Verify Latest Submitted Date is displayed at the top
// Verify: Verify the Submitted Date column is in descending order
page.getImportDateSubmittedColumnValues().then((results) => {
console.log(results);
});
});
});
});
});
The biggest thing was waiting for the different calls to get done running and then waiting for the stringDataArray to be filled. That required the promise(resolve,reject) structure I found in the SO post noted above. I ended up using the lambda (()=>{}) function calls instead of declared (function(){}) for a cleaner look, the method works the same either way. None of the other proposed solutions successfully propagated the array of strings back to my test. I'm working in Typescript, with Protractor.

Categories

Resources