ReactJS: Unable to parse through a 2d array outside async function - arrays

Trying to setState of an array in an async function, and accessing it inside the async function works fine. But when I try to access it outside the async function it only parses through the first layer meaning bookDetails[0], while parsing for bookDetails[0][0] gives an error as "cannot-read-property-0-of-undefined"
getAll = async () => {
const { contract } = this.state;
const response = await contract.methods.getBooks().call();
this.setState({ bookDetails: response});
console.log("books: ",this.state.bookDetails[0][0]);
};
//OUTPUT=> books: nanme
//console.log outside the async function gives error

notice that setState is not sync itself, you have to check the updated state in its callback:
getAll = async () => {
const { contract } = this.state;
const response = await contract.methods.getBooks().call();
this.setState({ bookDetails: response}, () => console.log("books: ",this.state.bookDetails[0][0]);
);};
Other than that, the snippet is not enough to verify why outsile async function you can't log correctly. Please provide a more complete snippet.

Related

Firestore Async function

So I'm trying to use React Query with Firestore and usually this works pretty fine but recently I have been unable to make a function that returns a value.
const fetchProduct = async () => {
const querySnapshot = await getDocs(collection(db, "notes"));
const arr = [];
querySnapshot.forEach((doc) => {
setNotes(doc.data())
}).catch((error) => {
console.log(error);
return null;
});
return notes
}
I'm later trying to use this function with React Query like this -
const { isLoading, isError, data, error } = useQuery(['todos'], fetchProduct)
However the value of the {data} always return to undefined but however once the fetchProduct() function is called manually it all works.
Is there anything I'm missing or doing wrong?
Setting the state is an asynchronous operation in React (see docs), so the value of notes isn't set yet by the time your return notes runs.
If you want to return the value synchronously:
return querySnapshot.docs.map((doc) => doc.data());
More typically though, you'll want to put the code that depends on that return value into its own useEffect hook that depends on the notes state.
Also see:
The useState set method is not reflecting a change immediately
Why does calling react setState method not mutate the state immediately?
Is useState synchronous?
It should be like this, you should not set inside the foreach function
const fetchProduct = async () => {
const querySnapshot = await getDocs(collection(db, "notes"));
const notes = [];
querySnapshot.forEach((note) => {
notes.push({ ...note.data(), id: note.id })
}).catch((error) => {
console.log(error);
return null;
});
return notes;
}
// in the place of the calling the function
const notes = await fetchProduct();
setNotes(notes);
Note: don't use variable name as doc in foreach, instead use some other variable name like since doc is built in function of firebase you might have imported it

Why my useEffect that tries to get blockchain data is looping infinitely and my async func still returns Promise pending

I am trying to use async await inside a useEffect hook getting some data from a testnet blockchain but I am getting 2 problems:
The async function returns a Promise, why is that? Shouldn't async await automatically resolve the promise and give me the data? I tried to solve it with Promise.resolve but not working, it still tells me campaigns is a Promise in pending state.
It enters in an infinite loop and I still do not get why.
Here is the code:
useEffect(() => {
const getCampaigns = async() => {
const campaigns = await factory.methods.getDeployedCampaigns().call()
return campaigns
}
const campaigns = getCampaigns();
setCampaigns(Promise.resolve(campaigns));
console.log('campaigns: ', campaigns);
})
You have no dependencies array.
useEffect(() => {
const getCampaigns = async() => {
const campaigns = await factory.methods.getDeployedCampaigns().call()
return campaigns
}
const campaigns = getCampaigns();
setCampaigns(Promise.resolve(campaigns));
console.log('campaigns: ', campaigns);
}, [])
Try this
useEffect(() => {
const getCampaigns = async() => {
const campaigns = await factory.methods.getDeployedCampaigns().call()
setCampaigns(campaigns);
}
getCampaigns();
}, []);
The empty array in useEffect call makes it behave like component did mount and only executes once (assuming factory methods are initialized on mount) and since the getDeployedCompanigns Promise is already resolved I'm simply setting the state in the getCampaigns function.
Read this article for details: https://devtrium.com/posts/async-functions-useeffect

How tu turn this function into a thunk react promise function (Updated)

I am fetching data from the blockchain. Contract adresses in this case.
Once I have the adresses I fetch some info on each specific adresses and add a key=>value pair to the object. This is all working and I'm getting all the right data. However, once in the component, the newly added key=>value pair is no longer present. I think this is because the value added is a promised and the dispatch is not waiting on it. How can I fix this so the dispatch it done only once the promised is resolved.
const tokenStream = await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'})
const allTokens = tokenStream.map((event) => event.returnValues)
console.log('ALL TOKEN DATA : ', allTokens)
allTokens.forEach( async element => {
let symbol = await exchange.methods.getERCsymbol(element.tokenAddress).call()
element.symbol = symbol
});
console.log('ALL TOKEN DATA AFTER : ',allTokens) // I see symbol
dispatch(allTokensLoaded(allTokens))
Better solution would be to use Promise.all to wait for multiple async request/promise to finish, and also you are mixing await and then, as your main function is already async you can write it in more neat and clean way using await only.
export const loadAllTokens = async (exchange, dispatch) => {
const result = await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'});
const allTokens = result.map((event) => event.returnValues);
await Promise.all(allTokens.map(async (element) => {
const innerResult = await exchange.methods.getERCsymbol(element.tokenAddress).call();
element.symbol = innerResult;
element[2]= innerResult;
}));
dispatch(allTokensLoaded(allTokens));
}
its more clean and better to understand :).
if any doubts please comment.
They happens to be nested async request and this is how I fixed it
export const loadAllTokens = async (exchange, dispatch) => {
await exchange.getPastEvents('OtherToken', {fromBlock:0, toBlock: 'latest'}).then( async(result) => {
const allTokens = result.map((event) => event.returnValues)
let count = 0
allTokens.forEach( async element => {
await exchange.methods.getERCsymbol(element.tokenAddress).call().then( async(result) =>{
element.symbol = result
element[2]= result
count += 1
}).then( () => {
if(count === allTokens.length){
dispatch(allTokensLoaded(allTokens))
}
})
})
})
}

How to test method calls after async call in React/Redux/Jest tests

My component has a button which call method 'handleSave'. I simplified the code to make it more relevant.
That component method looks like:
handleSave = async () => {
const response = await this.props.dispatchSave();
this.props.dispatchNotification();
}
My test:
let dispatchSave = jest.fn().mockResolvedValue({});
let dispatchNotification = jest.fn().mockReturnValue('Saved!');
it('should dispatch actions', () => {
const component = mount(<Comp dispatchSave={dispatchSave} dispatchNotification={dispatchNotification}>);
const instance = component.find(Comp).instance() as Comp;
instance.handleSave();
expect(dispatchSave).toHaveBeenCalled();
expect(dispatchNotification).toHaveBeenCalledWith('Saved!');
});
The first assertion works, but the second dispatch never gets asserted because it appears after an async call (if I move it above, it works).
How can I assert method calls after an async call?
If this.props.dispatchNotification returns a promise (or you could make it return a promise), then you could return this result in a handleSave call.
handleSave = async () => {
const response = await this.props.dispatchSave();
return this.props.dispatchNotification();
}
In test you need to prefix it with async keyword and await for your function call.
it('should dispatch actions', async () => {
const component = mount(<Comp dispatchSave={dispatchSave} dispatchNotification={dispatchNotification}>);
const instance = component.find(Comp).instance() as Comp;
await instance.handleSave();
expect(dispatchSave).toHaveBeenCalled();
expect(dispatchNotification).toHaveBeenCalledWith('Saved!');
});

Get data from a promise. Or is it promise at all?

I store some data in IndexedDB and i use npm package localforage for it.
const retrieveData = async () => {
const keys = await localforage.keys()
const data = await keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
})
return data
}
Whenever I execute this function, I get a resolved Promise object, which values I cannot extract
async componentDidMount() {
let asyncData = retrieveData()
console.log(asyncData) // Promise object
asyncData = retrieveData().then(values => values)
console.log(asyncData) // Promise object anyways
}
How exactly should I get data from this Promise object?
const retrieveData = async () => {
const keys = await localforage.keys()
// The return value of "keys.map" is an array of promises since
// async automatically returns a Promise behind the scenes.
// Await works on a single promise, not an array of promises,
// so "data" will not contain the actual data.
const data = await keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
})
return data
}
Do:
const retrieveData = async () => {
const keys = await localforage.keys()
const data = await Promise.all(keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
}));
return data
}
Or use Bluebird's map which works out of the box in this scenario:
// The "then" function does not do anything. It returns values,
// but only does so to the next "then" function. There are no
// further then-functions so the return value is unused.
// "values" is merely a local variable so you won't be able to
// access it anywhere outside the fat arrow function.
// You could move the console log into "then".
asyncData = retrieveData().then(values => values)
// asyncdata is still the unresolved promise object, the "then"
// function has not been run yet (then on the line above will be run when
// all of the awaits in retrieveData have been successfully resolved.
console.log(asyncData)
Do:
async componentDidMount() {
const data = await retrieveData();
console.log(data);
}
Or:
componentDidMount() {
retrieveData().then(values => {
console.log(values);
});
}
you can use for of loop here, and it will be simpler, we dont use await keyword directly in a map iterator, instead we can use promise.all as mentioned in above answer.
const retrieveData = async () => {
const keys = await localforage.keys();
let data;
for (let key of keys) {
const item = await localforage.getItem(key);
data.push([item.username, item.compamy, item.email, item.lastUpdate]);
}
return data;
}
Try using the "await" reserved keyword before your retrieveData() method at componentDidMount(), since it's a promise, an async event, you have to wait until it finishes all of it's inner executions to return some data and go on.
Just like you did at retrieveData() declaration use await before the promise. in detail what you need:
async componentDidMount() {
let asyncData = await retrieveData()
....
}

Resources