React setState callback return value - javascript

I'm new in React and I was looking to achieve this kind of flow:
// set the state
// execute a function `f` (an async one, which returns a promise)
// set the state again
// return the promise value from the previous function
So, what I'm doing now is the following:
async function handleSomething() {
this.setState((prevState) => { ... },
() => {
let result = await f()
this.setState((prevState) => { ... },
...
)
})
return result;
}
Hope you get the idea of what I want to achieve. Basically I want to get result, which is the value returned from awaiting f, and return it in handleSomething so I can use it in another place, but wrapping it up inside those setState calls:
// g()
// setState
// res = f()
// setState
// return res
My question is, how can I do this properly? Maybe should I modify the state with the result value and get it from there?.
EDIT:
Usage of handleSomething:
// inside some async function
let result = await handleSomething()

You can create a Promise that resolves once both setState calls are done:
function handleSomething() {
return new Promise(resolve => {
this.setState(
prevState => {
/*...*/
},
async () => {
let result = await f();
this.setState(
prevState => {
/*...*/
},
() => resolve(result)
// ^^^^^^^ resolve the promise with the final result
);
}
);
});
}
Which would be used like:
this.handleSomething().then(result => /* ... */)
// or
const result = await this.handleSomething();

Related

React componentDidMount setState but return undefined

I can get list with get axios.get method. then ı can use setState and it works flawlessly.
but the return is not true , its return undefined console.log(result) => undefined . How can ı check if setState work fine return true or return false ?
getList = async () => {
await axios.get("http://localhost:5000/list").then((response) => {
this.setState(
{
copy: response.data.list,
},
() => {
return true;
},
);
});
};
componentDidMount = () => {
this.getList().then((result) => {
console.log(result);
});
};
Your return true statement is in setState's post-set callback. It won't be propagated to the promise that's returned from getList; indeed, you return nothing from that function (you don't even return the Axios promise; if you did return that, you would get the response logged in your console.log, but it would be logged before the setState callback finished), so you get undefined in the console.log.
If you need getList to return a promise that resolves to true once the state has been set, you'll need
getList = () => {
return new Promise((resolve) =>
axios.get("http://localhost:5000/list").then((response) =>
this.setState(
{
copy: response.data.list,
},
() => resolve(true),
),
),
);
};
componentDidMount = () => {
this.getList().then((result) => {
console.log(result);
});
};
The second argument to set state can return true but, it's not going anywhere. You need to somehow use it, example:
const response = await axios.get("http://localhost:5000/list")
return new Promise((resolve) => {
this.setState({
copy : response.data.list
}, () => {
resolve(true);
})
})
now the chain will work because you are resolving the promise chain with true instead of returning it from the callback function

Combine async and sync function results properly

I try to combine two function values (from a() and b()), but the code is not waiting on the await-statement in function test as expected. Instead the result value prints directly the wrong result.
function resData(status, message) {
return { ok: status, message: message };
}
function a() {
return resData(true, 'A');
}
async function b() {
// simulate some long async task (e.g. db call) and then return the boolean result
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
}); }
await sleep(2500);
return resData(true, 'B');
}
async function isValid() {
const promises = [a, b].map(async (fnc) => { return await fnc().ok; });
const results = await Promise.all(promises);
// combine the results to one boolean value
return results.every(Boolean);
}
async function test() {
// not waiting here
const res = await isValid();
// prints directly - wrong result false
console.log('result', res);
}
test();
After the wrong result output it waits 2.5 seconds. I think it has to do with the function call of resData, but I couldn't figure it out by myself, where my async / await misunderstanding is. Thanks in advance.
Instead of awaiting for the function to be resolved, You are awaiting on value return by function.
async function isValid() {
const promises = [a, b].map(async (fnc) => {
//return await fnc().ok;
// You have to await first function then call `.ok` value
return (await fnc()).ok;
});
const results = await Promise.all(promises);
// combine the results to one boolean value
return results.every(Boolean);
}
Just to simplify the problem, Please check the below code.
async function test() {
return { ok: true };
}
async function main() {
// trying to await on undefined.. test().ok == undefined
console.log(await test().ok); // undefined
// first await function.. then return ok
console.log((await test()).ok); // true
}
main();
I'd say there is 2 1 problem with your code
a was not returning a promise, so you cant treat it as one - ignore turns out you can, who knew!
You cant await a boolean, so await fnc().ok made no sense.
I would suggest you
Make a return a promise even if its just a resolved promise
Execute just the methods in the map
Read the value of ok within the every call to determine if all promises resolved with this value set to true
With these changes, it works how I suspect you expected with a 2.5 second wait before writing to the console.
function resData(status, message) {
return { ok: status, message: message };
}
function a() {
return resData(true, 'A');
}
async function b() {
// simulate some long async task (e.g. db call) and then return the boolean result
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
}); }
await sleep(2500);
return resData(true, 'B');
}
async function isValid() {
const promises = [a, b].map(fnc => fnc());
const results = await Promise.all(promises);
// combine the results to one boolean value
return results.every(x => x.ok);
}
async function test() {
// not waiting here
const res = await isValid();
// prints directly - wrong result false
console.log('result', res);
}
test();
Async functions implicitly return a promise that will ultimately be resolved to the final value returned when the function execution ends.
So, you just need to call your functions inside your map callback to collect the promises. The array of results will be an array of resData objects. So, you finally check for each ok property to match your requirements.
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function resData(status, message) {
return {ok: status, message: message};
}
function a() {
return resData(true, 'A');
}
async function b() {
await sleep(2500);
return resData(true, 'B');
}
async function isValid() {
const promises = [a, b].map((fnc) => fnc());
const results = await Promise.all(promises);
return results.every((result) => result.ok);
}
async function test() {
const res = await isValid();
console.log('result', res);
}
test();

Recursive function in Redux

Trying to pass a reference to the recursive function to check if Redux action data fetch is complete, but getting function reference errors
const fetchAccountComplete = (state, accountNo) => { //state here is function reference
return new Promise(resolve => {
(function waitForFetchComplete(state, accountNo) {
const {isFetching, receivedAt} = state().account[accountNo] // getting state not a function here
if (!isFetching) return resolve()
setTimeout(waitForFetchComplete, 100)
})()
})
}
Is there a better way to return a promise to the caller function in Redux dispatch actions so that once the data is fetched, i need to do some other logic in other action.
Update 1:
should have been more clearer. There are two callers of this Request, Recieve actions on say Account data. First caller is directed similar to the above comment by you so waits until complete, second caller would not be doing the async call again and need to check if data fetch is complete so trying to see if recursive function with check on state so that promise can be resolved is being done
You could take advantage of promising chaining.
Example:
Have three actions like: IS_FETCHING, FETCH_SUCCESS, FETCH_ERROR.
IS_FETCHING:
Will simply set your state as pending (may be useful for showing a loading animation, for example).
FETCH_SUCCESS:
Will contain the result of the fetch to update the state. Will also clear the isUpdating flag
FETCH_ERROR:
Will contain any possible error due to the fetch (application or network error). Will also clear the isUpdating flag
Then, what you could do at application level is:
dispatch({type: IS_FETCHING, payload: data});
fetch(`https://MY-SERVER.com/?data=${data}`)
.then(response => response.json())
.then(json =>
dispatch({
type: isError(json) ? FETCH_RESULT : FETCH_ERROR,
payload: json
})
);
You could even benefit of action creators for the job.
Here is a good guide for that: https://redux.js.org/advanced/async-actions
If you have a function that returns a promise that is called multiple times with the same arguments then you can group that in a way so that the function is not called when it still has an unresolved promise and something tries to call it again with the same arguments.
Here is an example:
//group promise returning function
const createGroup = (cache) => (
fn,
getKey = (...x) => JSON.stringify(x)
) => (...args) => {
const key = getKey(args);
let result = cache.get(key);
if (result) {
return result;
}
//no cache
result = Promise.resolve(fn.apply(null, args)).then(
(r) => {
cache.done(key); //tell cache promise is done
return r;
},
(e) => {
cache.done(key); //tell cache promise is done
return Promise.reject(e);
}
);
cache.set(key, result);
return result;
};
//creates a cache that will remove cached value when
// Promise is done (resolved or rejected)
const createCache = (cache = new Map()) => {
return {
get: (key) => cache.get(key),
set: (key, value) => cache.set(key, value),
done: (key) => cache.delete(key),
};
};
//function that retuns a promise
const later = (time, value) => {
console.log('executing later with values', time, value);
return new Promise((r) =>
setTimeout(() => r(value), time)
);
};
//create group function with a cache that will remove
// cache key when promise is resolved or rejected
const groupAndRemoveCacheOnDone = createGroup(
createCache()
);
//grouped version of the later function
const groupedLater = groupAndRemoveCacheOnDone(later);
//testing the groped later
groupedLater(100, 8); //first call causes console.log
groupedLater(100, 8); //same arguments will not call later
groupedLater(100, 8); //will not call later
//will call later because arguments are not the same
// as the other calls
groupedLater(100, 'XX');
groupedLater(100, 8) //will not call later
.then((value) => {
console.log('resolved with:', value);
//this will call later because cache value is removed
// after promise is resolved
return groupedLater(100, 8);
})
.then(() => {
//testing with fetchAccountComplete
console.log(
'***** how many times is fetchAccountComplete called *****'
);
const fetchAccountComplete = (state, accountNo) => {
console.log(
'fetchAccountComplete called with',
accountNo
);
return new Promise((resolve) => {
(function waitForFetchComplete(state, accountNo) {
const {
isFetching,
receivedAt,
} = state().account[accountNo]; // getting state not a function here
if (!isFetching) return resolve();
setTimeout(
() => waitForFetchComplete(state, accountNo),
100
);
})(state, accountNo);
});
};
const data = {
account: [{ isFetching: true }],
};
const state = () => data;
const groupedFetchAccountComplete = groupAndRemoveCacheOnDone(
fetchAccountComplete
);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0);
groupedFetchAccountComplete(state, 0).then((resolve) =>
console.log('resolved')
);
data.account[0].isFetching = false;
});

Needs the clarity about async function

I am trying to get the value from setTimeout. but console gives no result. how to handle this async functions? can any one help me to understand this in right way?
here is my try:
async function getTheme() {
const value = 'abc'
setTimeout(() => {
return value;
}, 3000);
}
getTheme().then(time => console.log(time)); //getting no result.
That is because you're returning inside the setTimeout callback, which does not actually resolve the promise.
What you want is to instead return a promise instead:
function getTheme() {
const value = 'abc'
return new Promise(resolve => {
setTimeout(() => resolve(value), 3000);
})
}
There is no need to use async, since you are already returning a promise in the getTheme() function.
Of course, you can abstract the whole "waiting" logic into another function: then, you can keep the async if you wish:
function sleep(duration) {
return new Promise(resolve => setTimeout(resolve, duration));
}
async function getTheme() {
const value = 'abc';
await sleep(3000);
return value;
}

How to use promises instead of callback in reactjs?

i want to execute a function once the other function is completed. I have used callbacks but would like to use promises. But i am not sure how to go about it.
Below is the code,
this.set_function(this.save); //save is the callback method
set_function = (callback) => {
const some_var = {};
this.props.get_method.then(response => {
some_var.data = response;
this.setState({selected: some_var});
if(callback) {
callback(this.props.id, some_var_data);
}
});
};
save = (id, some_var) => {
const payload = {};
payload.some_var = [some_var];
client.update(id, payload)
.then((request) => {
this.save_data(id);
});
};
Here in the above code, once set_function is completed save function should be executed. As shown above it works with callback. How can i do the same with promises. Could someone help me with this?
The only trick there is that your callback expects two separate things (this.props.id and some_var_data). A promise can have only one fulfillment value — so you wrap those up as an object:
set_function = () => this.props.get_method.then(response => {
this.setState({selected: some_var});
return {id: this.props.id, data: response};
});
Notice that since you get a promise from this.props.get_method, we just chain off it.
(Your some_var_data was already an object, but it only had the data property, so I just included data in the result object directly.)
You'd use it like this:
set_function()
.then(({id, data}) => {
// use `id` and `data` here
})
.catch(error => {
// Handle error here
});
(Or don't include .catch and return the promise chain to something else that will handle errors.)
Or of course, if you used it in an async function:
const {id, data} = await set_function();
Make it promising, by returning the chained promise:
set_function = (callback) => {
return this.props.get_method.then(response => {
this.setState({selected: some_var});
return {id: this.props.id, some_var };
});
};
Then chain the other function:
this.set_function.then(this.save)
and finally desteucture the passed object:
save = ({ id, some_var }) => {
Await your promises
Using async ... await your function will return a promise that will resolve once your function is finished.
set_function = async () => {
const some_var = {};
const response = await this.props.get_method;
some_var.data = response;
this.setState({selected: some_var});
return [this.props.id, some_var_data];
};
When you call set_function it will return a promise so you can await or .then it. Like so:
this.set_function().then(this.save);
// where
save = ([ id, some_var ]) => {
...
}
In this case, you would have to have set_function return a Promise.
set_function = () => {
const some_var = {};
this.props.get_method.then(response => {
some_var.data = response;
this.setState({selected: some_var});
return Promise.resolve({id: this.props.id, some_var: some_var_data})
});
};
Now you can use it like this:
set_function().then(data => save(data)
Here's a jsfiddle you can play with.
https://jsfiddle.net/ctwLb3zf/

Categories