React async / await and setState not rerendering - javascript

I have a problem with rerender after getting result from web3 call - execution of smart contract. Code below:
this.setState({ loading: true });
await contractInstance.methods
.myMethod(params)
.send({ from: myAccount, gas: 10000000 })
.then(async function(receipt) {
let txHash = receipt.transactionHash;
...
// await saveToDb(thHash, ...)
this.setState({ dateToDisplay: myVar.publishDate, loading: false });
..
and the render is as below:
render() {
if (!this.state.loading) {
return (
...
{this.state.dateToDisplay}
I have other methods where this pattern works, but here I could not make it work. I tried to make setState async and await it, like:
setStateAsync(state) {
return new Promise(resolve => {
this.setState(state, resolve);
});
}
But does not help either.
Any ideas?

Why do you combine await and promises?
The point of await is to stop the execution at that point and wait for the promise to resolve. The const result = await promise; is a replacement for promise.then(result => ...).
You could do this:
const receipt = await contractInstance.methods
.myMethod(params)
.send({ from: myAccount, gas: 10000000 });
let txHash = receipt.transactionHash;
...
// await saveToDb(thHash, ...)
this.setState({ dateToDisplay: myVar.publishDate, loading: false });
In my opinion, this makes the code less complex and easier to follow what's going on and make sense of it.

You need to change Async function to arrow function or bind the function so that this will be available inside that function
await contractInstance.methods
.myMethod(params)
.send({ from: myAccount, gas: 10000000 })
.then(async receipt => {
let txHash = receipt.transactionHash;
...
// await saveToDb(thHash, ...)
this.setState({ dateToDisplay: myVar.publishDate, loading: false });
Or bind it
await contractInstance.methods
.myMethod(params)
.send({ from: myAccount, gas: 10000000 })
.then(async function(receipt) {
let txHash = receipt.transactionHash;
...
// await saveToDb(thHash, ...)
this.setState({ dateToDisplay: myVar.publishDate, loading: false });
}.bind(this))

Related

TypeScript - async await not wait until funtion fully executed?

I need to fetch data from the API after getting the username, password and title from storage. Hence I use async await thrice before finally calling the API.
The data can be fetched correctly, but somehow the refresh timeout function completed first before the getData function fully executed.
doRefresh(event) {
setTimeout(async () => {
await this.storage.get('username').then(async username => {
await this.storage.get('password').then(async password => {
await this.storage.get('title').then(async tempName => {
await this.myService.getJSON(username, password, tempName, "keywordA", this.section).then(async ()=> {
await this.myService.getJSON(username, password, tempName, "keywordB", "trainingInfo"); }).finally(async ()=> {
await this.getData();
console.log('done');
event.target.complete();
});
})
})
});
}, 2500);
}
Have tried to remove async await, and put the code like this, but still not working as expected. Seems like console.log / event.target.complete always execute first before waiting getData function complete.
doRefresh(event) {
setTimeout(() => {
this.storage.get('username').then(username => {
this.storage.get('password').then(password => {
this.storage.get('title').then(tempName => {
this.myService.getJSON(username, password, tempName, "keywordA", this.section).then(()=> {
this.myService.getJSON(username, password, tempName, "keywordB", "trainingInfo"); }).finally(()=> {
this.getData();
console.log('done');
});
})
})
});
event.target.complete();
}, 2500);
}
In the first example, you're mixing await and .then, best to use one or the other.
Here's it re-written with async/await, it's much more readable in my opinion.
async () => {
try {
const username = await this.storage.get('username')
const password = await this.storage.get('password')
const tempName = await this.storage.get('title')
await this.myService.getJSON(username, password, tempName, "keywordA", this.section)
await this.myService.getJSON(username, password, tempName, "keywordB", "trainingInfo")
await this.getData();
console.log('done');
event.target.complete();
} catch (e) {
console.log("something went wrong")
}
}

Do I need to use await for async actions in react components?

I made this store:
export class CommentStore {
comments = []
constructor() {
makeAutoObservable(this, {}, { autoBind: true });
}
async loadPostComments(postId: number): Promise<void> {
const res = await API.loadPostComments(postId);
runInAction(() => {
this.comments = res;
});
}
async sendComment(postId: number, comment: Comment): Promise<void> {
try {
await API.sendComment(postId, comment);
await this.loadPostComments(postId);
return true;
} catch (err) {
console.log('oops');
}
}
}
Do i need use await in react components? For example:
useEffect(() => {
(async function () {
await loadPostComments(postId);
})();
}, [loadPostComments, postId]);
But this also works fine:
useEffect(() => {
loadPostComments(postId);
}, [loadPostComments, postId]);
Same for sendComment onClick:
onClick={()=>{sendComment(postId, comment)}}
onClick={async ()=>{await sendComment(postId, comment)}}
So, is it necessary to use await in this situations?
You want to await something only if it is necessary, e.g. when the next line of code uses the data from Promise.
In the useEffect case that you provided it is not necessary and on onClick handlers as well
Yes it is unnecessary to write async/await on them.
you just have to write the async call on the functions and that is enough.
for example:
const [posts, setPosts] = useState([]);
useEffect(() => {
const loadPost = async () => {
// Await make wait until that
// promise settles and return its result
const response = await axios.get(
"https://jsonplaceholder.typicode.com/posts/");
// After fetching data stored it in some state.
setPosts(response.data);
}
// Call the function
loadPost();
}, []);
`
there is no need to write promise and async / await on everything, remember ;P

Javascript async function return then-catch promise?

Introduction
I am new in the world of javascript promises and I need to understand how they work correctly...
Until know I have been doing this in my code:
const handleRefresh = () => {
setIsRefreshing(true);
fetchUserData()
.then(async () => { <--------- Using then because I return a promise in fetchUserData
await fetchUserPosts(); <------- Using await here
setIsRefreshing(false);
}).catch(err => { <--------- This will catch the error I have thrown in the function fetchUserPosts or inside the then body
// TODO - Show error
setIsRefreshing(false);
console.log(err)
);
};
const fetchUserData = async () => { <------ async function
const { firebase } = props;
const userId = firebase.getCurrentUser().uid;
const documentRef = firebase.getDatabase().collection("users").doc(userId);
// Fetch all the user information
return documentRef <--------- Returning the promise here
.get()
.then((doc) => {
if (doc.exists) {
// Get all user data
const data = doc.data();
console.log(data);
setUserData(data);
}
})
.catch((err) => {
throw err; <----------- Throwing error
});
};
I don't know if I am doing anti patterns... But basically I need to know if this is a good way and if I am doing this correctly.
Questions
Do I have to declare the fetchUserData function as async to return a promise?
Can I use the async await in the then/catch body?
Can I do this?
const handleRefresh = async () => {
setIsRefreshing(true);
await fetchUserData()
.then(async () => { <--------- Using then because I return a promise in fetchUserData
await fetchUserPosts(); <------- Using await here
}).catch(err => { <--------- This will catch the error I have thrown in the function fetchUserPosts or inside the then body
// TODO - Show error
console.log(err)
);
setIsRefreshing(false);
};
I would really appreciate if someone guides me. Thanks.
the words async and await are only syntactic sugar for then and catch.
This:
fetchUserData()
.then(data => return data )
.catch(error => return error)
is equivalent to:
async function getUserData() {
const userData = await fetchUserData()
return userData
}
Here you are returning anything (success or error). If you want to treat the error here, just put a try/catch clause.
async function getUserData() {
try {
return await fetchUserData()
} catch (e) {
return e.message
}
}
Note that you can only use the await clause within an async function.
1.
Function can return Promise without being declared as async as long as you don't await inside it,
2.
You should not use async-await inside then, simply return a Promise and it'll be resolved in the following then,
3.
When using async-await syntax, Promises are awaited in a declarative way as demonstrated below:
const handleRefresh = async () => {
try
{
const a = await getA()
// pass the result to another Promise
const b = await getB(a)
const c = await getC(b)
} catch (error)
{
handleError(error)
}
};

How to run JavaScript functions in order (React)

I have created a small react App that presents documents from a SharePoint site.
To make the App work correctly I am having to add setTimeouts, but I know there must be a better way using callbacks or promises or something >.<
My knowledge is lacking, so could somebody please point me in the right direction?
// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
// Updates dropdown select
this.setState({ selectedOption: e.target.value, isLoading: true });
// Checks security permissions - MAKES A GET REQUEST
setTimeout(() => {
this.getSecurityGroupUsers();
}, 1000);
// Ghecks if user can access docs
setTimeout(() => {
this.checkForDocAccess();
}, 2000);
// Gets documents - MAKES A GET REQUEST
setTimeout(() => {
this.getDocuments();
}, 4000);
// Delete Mark as Reviewed property THEN displays docs
setTimeout(() => {
this.hideMarkAsReviewed();
}, 8000);
}
One of the functions:
// Grabs the documents
public getDocuments() {
axios
.get("https://bpk.sharepoint.com/_api/search/query?querytext='owstaxIdDocumentx0020Owner:" + this.state.selectedOption + "'&trimduplicates=true&rowsperpage=100&rowlimit=1000&selectproperties='LastReviewDateOWSDATE,ScheduledReviewDateOWSDATE,Path'",
{ params:{},
headers: { 'accept': 'application/json;odata=verbose' }
})
.then(response =>
response.data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results.map(document => ({
Type: this.checkFile(document),
Name: document.Cells.results[6].Value.split("/").pop(),
'Scheduled Review Date': document.Cells.results[8].Value.slice(0,-11),
Path: document.Cells.results[6].Value.replace('https://bpkintelogydev.sharepoint.com', ''),
Site: document.Cells.results[6].Value.split('/').slice(4).slice(0,1),
'Last Review Date': document.Cells.results[7].Value.slice(0,-11),
View: <a href="#" onClick={()=>window.open(document.Cells.results[6].Value + '?web=1&action=edit')}>View</a>,
'Mark as Reviewed': <a href='#'>Mark</a>
}))
)
.then(documents => {
this.setState({documents, isLoading: true});
})
.catch(error => {
//console.log(error);
});
}
Yes, callbacks or promises are what you do here. Promises would be the more modern way (not least so you can use async/await with them). setState provides a callback for when the state is set if you want that (it's an optional second argument to setState). Then your various functions (getSecurityGroupUsers, etc.) would return promises that they fulfill or reject.
In your getDocuments, for instance, you want to:
return the result of calling then, and
Don't have an error handler, leave that to handleDropdownChange
...and similar in the others. For any that don't already have a promise to chain on (getDocuments has the one from axios) because they use a callback-style API you can create a promise and fulfill/reject it yourself (see this question's answers for details there).
Doing that, handleDropdownChange might look something like:
// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
// Updates dropdown select
this.setState({ selectedOption: e.target.value, isLoading: true });
this.getSecurityGroupUsers()
.then(() => this.checkForDocAccess())
.then(() => this.getDocuments())
.then(() => this.hideMarkAsReviewed())
.catch(error => {
// ...handle/report error...
});
}
or if you didn't want to start those until the state had been changed initially:
// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
// Updates dropdown select
this.setState(
{ selectedOption: e.target.value, isLoading: true },
() => {
this.getSecurityGroupUsers()
.then(() => this.checkForDocAccess())
.then(() => this.getDocuments())
.then(() => this.hideMarkAsReviewed())
.catch(error => {
// ...handle/report error...
});
}
);
}
In this particular case, async/await doesn't help much, but perhaps some. I assume handleDropdownChange shouldn't be async since it's an event handler and so nothing would handle the promise it would return, so:
// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
// Updates dropdown select
this.setState({ selectedOption: e.target.value, isLoading: true });
(async () => {
try {
await this.getSecurityGroupUsers();
await this.checkForDocAccess();
await this.getDocuments();
await this.hideMarkAsReviewed();
} catch (error) {
// ...handle/report error...
}
})();
}
Notice how the entire body of the async function is in a try/catch, because nothing hooks its promise, so it's important we handle errors directly. (Ensure that "handle/report error" doesn't throw an error. :-) )
You can use Async await for waiting promise to resolve
public handleDropdownChange = async e => {
this.setState({ selectedOption: e.target.value, isLoading: true });
await this.getSecurityGroupUsers();
await this.checkForDocAccess();
await this.getDocuments();
await this.hideMarkAsReviewed();
}
You need a function call back!
function functionOne(x) { return x; };
function functionTwo(var1) {
// some code
}
functionTwo(functionOne);
Use async await and a simple sleep function to wait between them if it is required
const sleep = time => new Promise(res => {
setTimeout(res, time);
});
async handleDropdownChange(e) {
// Updates dropdown select
this.setState({ selectedOption: e.target.value, isLoading: true });
// Checks security permissions - MAKES A GET REQUEST
await sleep(1000);
await this.getSecurityGroupUsers();
await sleep(1000);
// Ghecks if user can access docs
await this.checkForDocAccess();
await sleep(1000);
// Gets documents - MAKES A GET REQUEST
await this.getDocuments();
await sleep(1000);
// Delete Mark as Reviewed property THEN displays docs
await this.hideMarkAsReviewed();
}
// But you methods should return a promise like this
getDocuments = () => {
return fetch('some url').then(res => res.json());
}
// Or something like
getSecurityGroupUsers = async () => {
const data = await axios('some url');
return data.message;
}

How to map over data that `.find()` returns?

I have a simple function:
const oldUsers = Users.find(
{
isReminded: false,
creationDate: {
"$lt": new Date(Date.now() - thirtyDays),
},
},
);
and then:
export const notifyOldUsers = () =>
Array.isArray(oldUsers) ? oldUsers.map(async(user, i) => {
await Users.updateOne({ _id: user._id }, { "$set": { isReminded: true }});
await transporter.sendMail(sendMail));
}) : [];
};
But the problem is that oldUsers returns object, and if I console.log it, its a complicated Query object.
Question: How to to properly loop over the data, which .find() produces?
First, the Users.find() is an async operation, so oldUsers is always going to be undefined. You need to await it (if you weren't using promises you'd have to use a callback).
const oldUsers = await Users.find(...)
Second, your notifyOldUsers function is not async, so it runs the mapping function, but exits immediately, not waiting for the mapped async operations to finish.
Typically, when mapping async operations, you should use a Promise.all() to collect all the promises returned by the callback function and wait for them all to resolve.
export const notifyOldUsers = async () => {
const promises = oldUsers.map(async (user) => {
await Users.updateOne({ _id: user._id }, { "$set": { isReminded: true }})
await transporter.sendMail(sendMail))
})
return Promise.all(promises)
}
I've intentionally split up the mapping from the Promise.all() to illustrate the map of promises being returned. This can be further optimized to the following.
export const async notifyOldUsers = async () => {
return Promise.all( oldUsers.map(async (user) => {
await Users.updateOne({ _id: user._id }, { "$set": { isReminded: true }})
await transporter.sendMail(sendMail))
}) )
}
In both cases, this async function will return a new promise who's value will be an array containing the results of the entire map.

Categories