How can I make asynchronous code to execute before continuing [duplicate] - javascript

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 3 years ago.
I'm quite new to hooks and in react in general. I came accross a problem I couldn't solve so here it is. I have a quotation function that is suppose to calculate the cost price of a benefit.
const quotation = useCallback(()=> {
setCostPrice(0)
keys.forEach(e => {
for (const property in values){
if ((property==e && values[property]==true){
setCostPrice(costPrice + pricing[property])
}
}
})
})
Here is my problem, i'm calling the function quotation() everytime a checkbox is checked (so the cost price will update). The problem is, the function is suppose to "reset" the costprice at 0 everytime it is called but since using "setCostPrice(0)" is asynchronous the result is not the right one. I've read different post concerning using useEffect() but when i'm using useEffect the function can't read the value from values[property].
I don't know how I can fix this issue if anyone wants to help I would appreciate !

If you need to wait for some asynchronous actions, just make your function async and use await inside:
const quotation = useCallback(async ()=> {
await setCostPrice(0)
...
})
Or use .then() if you're not able to apply async/await:
const quotation = useCallback(()=> {
setCostPrice(0).then(() => {
keys.forEach(e => {
...
});
});
})
If this setCostPrice() comes from useState() hook like that:
const [ , setCostPrice ] = useState(...);
You can just provide callback there to receive latest state;
setCostPrice(
oldPrice => { /* return new price from here */ }
);
By the way, you need to provide list of deps for useCallback() hook. Otherwise it won't work.

Related

How can I update a state variable from a promise?

I am trying to determine if a customer has an active subscription or not. To do this I am utilizing the following code:
const stripe = require('stripe')('some-api-key');
export default function Example(){
// the user will automatically be considered non-subbed by default
const [isSubscriber, setIsSubscriber] = useState(false)
// grab the customer id from stripe
async function get_customer_id() {
const customers = await stripe.customers.search({
query: `metadata[\'some-meta-data-key\']:\'some-meta-data-value\'`
});
return customers.data[0]['id']
}
// grab the list of active subscriptions from stripe
async function customer_is_subscriber(){
const subs = await stripe.subscriptions.list({
status: 'active',
});
return subs
}
// determine if the customer id is in the list of active subscriptions.
// return true if so, false otherwise
async function test_equality(){
const customer_id = await get_customer_id();
const subbed = await customer_is_subscriber();
const answer = subbed.find(sub => sub.customer === customer_id)
return !!answer;
}
useEffect( () => {
async function load_result() {
const promise_function_return = await test_equality()
setIsSubscriber(promise_function_return)
}
load_result();
}, [isSubscriber]);
return (
// some react code
)
}
I have been able to successfully get all of my other functions where I am doing the comparisons for if a user is a subscriber but where I am having an issue is updating the state value (e.g. true if they are subbed, false otherwise).
I found some good past questions on this specific topic such as:
here The useState set method is not reflecting a change immediately
here: setState inside Promise in React
and here: setState inside a Promise function in a useEffect with hooks?
but I just have not been able to get it working correctly. This is currently the closest I have been able to get to solving this problem.
Currently your code says that, when isSubscriber changes, it should check if the user is a subscriber and update the isSubscriber state... so it's a chicken and egg problem. It won't set isSubscriber until isSubscriber gets set.
I think you want to change }, [isSubscriber]); to }, []); so that that code executes when the component first loads (not when isSubscriber changes).
The useEffect hook will always run on mount regardless of if there is anything in its dependency array. This means that your useEffect will work as is, and will run onMount as well as when isSubscriber changes:
useEffect( () => {
async function load_result() {
const promise_function_return = await test_equality()
setIsSubscriber(promise_function_return)
}
load_result();
}, [isSubscriber]);
To verify this, you can check out this codesandbox example. The useEffect looks just like yours, and you will notice that isSubscriber is initially set to false, but is updated to true after 3 seconds.
There's still an adjustment you may want to make even though that part appears to work ok. With isSubscriber in the dependency array, the function in your useEffect will be called any time isSubscriber changes. This probably not what you want, since this function doesn't actually depend on isSubscriber, but actually sets isSubscriber. In this case, that means test_equality() will be run on initial mount and then one more time after it sets isSubscriber, which is unnecessary.
This blog post explains the useEffect dependency array really well.
You can fix this by removing isSubscriber from the dependency array, like this:
useEffect(() => {
console.log("in useEffect");
async function load_result() {
const promise_function_return = await test_equality();
setIsSubscriber(promise_function_return);
}
load_result();
}, [isSubscriber]);
Since you mentioned the state value is not getting updated, there must be another issue going on in either get_customer_id() or customer_is_subscriber(). It would be good to double check and make sure the stripe api calls are working as expected.

How to save response data in a variable or state in js [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 1 year ago.
i'm trying to use trello-web in react.
in the docs it say:
When you have a token, you can do .get, .put, .post and .del, for example:
trello.get('/1/boards').then(console.log)
and everything works fine (with console.log).
But if i want save the response in a variable what i have to change in this line of code to use it in my component?
I dont understand that
.then(console.log)
print in my console and so i cant save it to reuse correctly.
If someone help me to understand.. thanks very much :)
.then(console.log) is a shorthand of .then(event => console.log(event)) (or rather, data). You can write your own function that takes that event as an argument, then do whatever you want with it, for example:
.then(save)
function save(event) {
//do stuff with the event
}
I know you said React, but this should get you going.
If u are using react u can make a state in component eg.
[myData, setMyData] = useState();
Now you can handle incoming data by
trello.get('/1/boards')
returns you a "Promise", you can handle it in two ways
1.
var data = async trello.get('/1/boards')
setMyData(data)
trello.get('/1/boards').then( (data) => { setMyData(data) } )

Do react query result objects go stale due to closures?

I have always felt like I have a pretty solid understanding of how JS closures work, but since using ReactJS for the last couple of months there is something I can't quite make sense of.
In the below example, will the query result object getData be stale if used in the success callback for the other query?
My closure instincts tell me yes, but my reactjs instincts tell me no for some reason and I can't make sense of why it works the way that it does.
All help is appreciated!
/*
* Custom query hooks
*/
function useGetData(someId, opts){
return useQuery(['getData', someId],
getDataQueryFn,
{
...opts,
},
);
}
function useOtherData(someId, opts){
return useQuery(['otherData', someId],
otherDataQueryFn,
{
...opts,
},
);
}
/*
* React component
*/
function MyComponent(){
const getData = useGetData("1");
const otherData = useOtherData("1", {
onSuccess: () => {
// QUESTION: if I use getData here, won't it be stale due to closure? why or why not?
}
});
// render and stuff...
}
It doesn't go stale. Here's a codesandbox that closures over a counter and the fetch needs 5 seconds to complete. relevant code from the sandbox:
const [count, inc] = React.useReducer((val) => val + 1, 0);
const { data } = useQuery(
"repoData",
async () => {
await waitFor(5000);
return Promise.resolve({ name: "foo " });
},
{
onSuccess: () => console.log(count)
}
);
if you click the counter a couple of times in the first 5 seconds, the latest count will still be logged. It doesn't really matter if you closure over local state or over some other query.
The reason is likely that react-query will only trigger a re-render after the Promise is successful (it's a simple forced re-render via useState under the hood), and at that time, react already "sees" the latest value, not the value from the time when the query started to fetch.
It depends on useQuery implementation. However I am pretty sure it wont use stale data. I expect following order:
Each time result of useGetData is updated - re-render happens, due to some internal state update
new onSuccess callback (capturing latest getData result in closure) is passed via props to useOtherData and picked up by library to handle next update

React state is set function is not working [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 1 year ago.
I'm building a web page that uses fetch to search information and it shows it to the user.
I've decided to store that information in a state but for some reason it doesn't work the way I expected.
When the data arrives I use setText() and then I print both the 'text' and 'data' variables.
Only 'data' is printing something, 'text' doesn't show anything.
Anyone knows what can be the problem here?
const [text, setText] = useState("")
const getTextFromApi = async () => {
const resp = await fetch(endpoint)
const data = await resp.json()
setText(data)
console.log(data)
console.log("------------------------")
console.log(text)
}
In your example, setText is a function that queues another render of your function and updates the value that will be used for text in that and subsequent renders. It does not update the value of the text variable, which is a local variable.
Edit: this is explained in much more depth here: useState set method not reflecting change immediately
Problem is that set state functions are asynchronous so you don't have the certainty that when you console log the value of the state it was already updated
you could do something like this inside your component
useEffect(() => {
what you execute here will already have the text value set
},[text])

How can I change the state of arrays using hooks?

I don't know exactly what it is, but I have run into countless problems in trying to do the simplest state updates on arrays using hooks.
The only thing that I have found to work is using the useReducer to perform a single update on the array with putting dispatch on onClick handlers. In my current project, I am trying to update array state in a for loop nested in a function that runs on a form submit. I have tried many different solutions, and this is just one of my attempts.
function sessionToState(session) {
let formattedArray = []
for (let i = 0; i < session.length; i++) {
formattedArray.push({ url: session[i] })
setLinksArray([...linksArray, formattedArray[i]])
}
}
// --------------------------------------------------------
return (
<div>
<form
method="post"
onSubmit={async e => {
e.preventDefault()
const session = await getURLs({ populate: true })
sessionToState(session)
await createGroup()
I was wondering if there are any big things that I am missing, or maybe some great tips and tricks on how to work with arrays using hooks. If any more information is needed don't hesitate to ask. Thanks.
I was wondering if there are any big things that I am missing
TLDR: setLinksArray does not update linksArray in the current render, but in the next render.
Assuming the variables are initialized as follows:
const [linksArray, setLinksArray] = useState([])
A hint is in the const keyword, linksArray is a constant within 1 render (and this fact wouldn't change with let, because it's just how useState works).
The idea of setLinksArray() is to make a different constant value in the next render.
So the for loop would be similar to:
setLinksArray([...[], session0])
setLinksArray([...[], session1])
setLinksArray([...[], session2])
and you would get linksArray = [session2] in the next render.
Best way to keep sane would be to call any setState function only once per state per render (you can have multiple states though), smallest change to your code:
function sessionToState(session) {
let formattedArray = []
for (let i = 0; i < session.length; i++) {
formattedArray.push({ url: session[i] })
}
setLinksArray(formattedArray)
}
Furthermore, if you need to perform a side effect (like an API call) after all setState functions do their jobs, i.e. after the NEXT render, you would need useEffect:
useEffect(() => {
...do something with updated linksArray...
}, [linksArray])
For a deep dive, see https://overreacted.io/react-as-a-ui-runtime
When invoking state setter from nested function calls you should use functional update form of setState. In your case it would be:
setLinksArray(linksArray => [...linksArray, formattedArray[i]])
It is not exactly clear what kind of problems you encounter, but the fix above will save you from unexpected state of linksArray.
Also this applies to any state, not only arrays.
Performance wise you shouldn't call setState every iteration. You should set state with final array.
const sessionToState = (session) => {
setLinksArray(
session.map(sessionItem => ({url: sessionItem}))
);
}
... or if you want to keep old items too you should do it with function inside setState ...
const sessionToState = (session) => {
setLinksArray(oldState => [
...oldState,
...session.map(sessionItem => ({url: sessionItem}))
]);
}

Categories

Resources