Wait for function to setState before triggering next function - javascript

async componentDidMount() {
this.loadSelectors();
this.useSelectors();
};
loadSelectors = () => {
this.setState({"Selector": "Test"});
}
useSelectors = () => {
console.log(this.state.Selector);
}
How do I wait for loadSelectors() to finish setting the state before I call useSelectors()? I know the normal way to do this is
this.setState({"Selector": "Test"}, => this.useSelectors());
but my case seems different, I've tried
async componentDidMount() {
await this.loadSelectors(() => useSelectors());
}
and It does not work.

It seems you want to promisify this.setState, but loadSelectors does not return anything. It should return a promise that resolves when the callback -- that should be passed to setState -- is called:
var loadSelectors = () => new Promise(resolve =>
this.setState({"Selector": "Test"}, resolve)
);
One rule to follow when you use await: if the goal is to await a certain future event, then the expression that follows it should be a promise, as otherwise there is not going to be much awaiting.

I managed to figure out what I needed, just pass the useSelectors function as a callback function and use it in loadSelectors
componentDidMount() {
this.loadSelectors(() => this.useSelectors());
};
loadSelectors = (callbackFunction) => {
this.setState({"Selector": "Test"}, callbackFunction);
}
useSelectors = () => {
console.log(this.state.Selector);
}

Related

Complete one promise array first, then complete another promise array JavaScript

I have a problem with promises and have no idea how resolve this:
My idea is to have two methods that are a "dispatcher". In testFetchFaceCharacter() call all promises that I need to resolved first. Need all data from state: {Body:{}, TopA:{}, eyes:{}} When testFetchCharacter() finished, immediately start testFetchTopCharacter(), only if all previous promises executed successfully.
However, at this point (with this code) have a errors. The promises aren't executed "Synchronously". still retrieved "asynchronously". Which "should not happen". Since it "reduce" (from what I read in several articles) avoided that behavior.
const buildCharacter = (state) => {
try {
testFetchFaceCharacter(state);
testFetchTopCharacter(state);
} catch (e) {
console.error(e + "En buildCharacter");
}
const testFetchCharacter = (state) => {
const promises = [
fetchCustom(state.Body.id, state.Body.file),
fetchCustom(state.TopA.id, state.TopA.file),
fetchCustom(state.eyes.id, state.eyes.file),
fetchCustom(state.mouth.id, state.mouth.file),
fetchCustom(state.nose.id, state.nose.file),
fetchCustom(state.eyebrow.id, state.eyebrow.file),
fetchCustom(state.Clothing.id, state.Clothing.file),
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
const testFetchTopCharacter = (state) => {
const promises = [
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce(async (previousPromise, nextPromise) => {
await previousPromise
return nextPromise
}, Promise.resolve());
}
Y try this:
Execute -> Body
Execute -> TopA
Execute -> [eyes, mouth, nose, Clothing, eyebrow] //No matter the order
then
Execute [beard, hat, hair, glass] //not matter the order
First of all, there is a mistake in your code. You need to understand that as soon as you called a function, you triggered a logic that does something, even if you don't listen to the promise right away, the logic is executing.
So what happened, is that you launched all actions in "parallel" when you are doing function calls in the promises array.
Solution A
You need to "postpone" the actual call of a function until the previous function was successful, you can either do it manually, e.g.
const testFetchTopCharacter = async (state) => {
await fetchCustom(state.beard.id, state.beard.file),
await fetchCustom(state.hat.id, state.hat.file),
await fetchCustom(state.hair.id, state.hair.file),
await fetchCustom(state.glass.id, state.glass.file)
}
Solution B
If you want to use reducer you need to use callback in that array, so that when promise is completed you call the next callback in the chain.
const testFetchTopCharacter = (state) => {
const promises = [
() => fetchCustom(state.beard.id, state.beard.file),
() => fetchCustom(state.hat.id, state.hat.file),
() => fetchCustom(state.hair.id, state.hair.file),
() => fetchCustom(state.glass.id, state.glass.file)
];
promises.reduce((promise, callback) => promise.then(callback), Promise.resolve());
}
Solution C
If an order doesn't matter to you just do Promise.all
const testFetchTopCharacter = (state) => {
return Promise.all([
fetchCustom(state.beard.id, state.beard.file),
fetchCustom(state.hat.id, state.hat.file),
fetchCustom(state.hair.id, state.hair.file),
fetchCustom(state.glass.id, state.glass.file)
]);
}

Using useEffect with async?

I'm using this code:
useFocusEffect(
useCallback(async () => {
const user = JSON.parse(await AsyncStorage.getItem("user"));
if (user.uid) {
const dbRef = ref(dbDatabase, "/activity/" + user.uid);
onValue(query(dbRef, limitToLast(20)), (snapshot) => {
console.log(snapshot.val());
});
return () => {
off(dbRef);
};
}
}, [])
);
I'm getting this error:
An effect function must not return anything besides a function, which
is used for clean-up. It looks like you wrote 'useFocusEffect(async ()
=> ...)' or returned a Promise. Instead, write the async function inside your effect and call it immediately.
I tried to put everything inside an async function, but then the off() is not being called.
Define the dbRef variable outside the nested async function so your cleanup callback can reference it, and allow for the possibility it may not be set as of when the cleanup occurs.
Also, whenever using an async function in a place that doesn't handle the promise the function returns, ensure you don't allow the function to throw an error (return a rejected promise), since nothing will handle that rejected promise.
Also, since the component could be unmounted during the await, you need to be sure that the async function doesn't continue its logic when we know the cleanup won't happen (because it already happened), so you may want a flag for that (didCleanup in the below).
So something like this:
useFocusEffect(
useCallback(() => {
let dbRef;
let didCleanup = false;
(async() => {
try {
const user = JSON.parse(await AsyncStorage.getItem("user"));
if (!didCleanup && user.uid) {
dbRef = ref(dbDatabase, "/activity/" + user.uid);
onValue(query(dbRef, limitToLast(20)), (snapshot) => {
console.log(snapshot.val());
});
}
} catch (error) {
// ...handle/report the error...
}
})();
return () => {
didCleanup = true;
if (dbRef) {
off(dbRef);
}
};
}, [])
);

React setState callback return value

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();

Does this.setState return promise in react

I made my componentWillMount() async. Now I can using await with the setState.
Here is the sample code:
componentWillMount = async() => {
const { fetchRooms } = this.props
await this.setState({ })
fetchRooms()
}
So question here is this.setState returns promise because I can use await with it?
Edit
When I put await then it runs in a sequence 1, 2, 3 And when I remove await then it runs 1, 3, 2??
componentWillMount = async() => {
const { fetchRooms } = this.props
console.log(1)
await this.setState({ } => {
console.log(2)
})
console.log(3)
fetchRooms()
}
You can promisify this.setState so that you can use the React API as a promise. This is how I got it to work:
class LyricsGrid extends Component {
setAsyncState = (newState) =>
new Promise((resolve) => this.setState(newState, resolve));
Later, I call this.setAsyncState using the standard Promise API:
this.setAsyncState({ lyricsCorpus, matrix, count })
.then(foo1)
.then(foo2)
.catch(err => console.error(err))
setState is usually not used with promises because there's rarely such need. If the method that is called after state update (fetchRooms) relies on updated state (roomId), it could access it in another way, e.g. as a parameter.
setState uses callbacks and doesn't return a promise. Since this is rarely needed, creating a promise that is not used would result in overhead.
In order to return a promise, setState can be promisified, as suggested in this answer.
Posted code works with await because it's a hack. await ... is syntactic sugar for Promise.resolve(...).then(...). await produces one-tick delay that allows to evaluate next line after state update was completed, this allows to evaluate the code in intended order. This is same as:
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, () => {
console.log(2)
})
setTimeout(() => {
console.log(3)
});
There's no guarantee that the order will stay same under different conditions. Also, first setState callback isn't a proper place to check whether a state was updated, this is what second callback is for.
setState does not return a promise.
setState has a callback.
this.setState({
...this.state,
key: value,
}, () => {
//finished
});
It does not return a promise.
You can slap the await keyword in front of any expression. It has no effect if that expression doesn't evaluate to a promise.
setState accepts a callback.
Don't think setState is returning a Promise but you can always do this
await new Promise( ( resolve ) =>
this.setState( {
data:null,
}, resolve )
)
or you can make some utility function like this
const setStateAsync = ( obj, state ) => {
return new Promise( ( resolve ) =>
obj.setState( state , resolve )
)
}
and use it inside a React.Component like this:
await setStateAsync(this,{some:'any'})
You can simple customize a Promise for setState
componentWillMount = async () => {
console.log(1);
await this.setRooms();
console.log(3);
};
setRooms = () => {
const { fetchRooms } = this.props;
return fetchRooms().then(({ room }) => {
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, _ =>
console.log(2)
);
});
};
Or
setRooms = async () => {
const { fetchRooms } = this.props;
const { room } = await fetchRooms();
return new Promise(resolve => {
this.setState({ roomId: room && room.roomId ? room.roomId : 0 }, _ =>
resolve()
);
});
};
Hope this help =D
Simpler way to answer this question is we can use promisify function present in pre-installed util library of node.js and then use it with the await keyword.
import {promisify} from 'util';
updateState = promisify(this.setState);
await this.updateState({ image: data });

best way to stack function calls in componentDidMount

componentDidMount = () => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.props.syncFirebaseToStore(user);
thenCallThis()
thenCallThis2()
}
}
whats the best way to do the above? basically I want to call 3 functions but only after the previous one has finished executing?
I tried using resolve new Promise but don't think I quite have the syntax right. I would like to chain it with .then() ideally
With promises:
componentDidMount = () => {
return firebase.auth().onAuthStateChanged((user) => {
if (!user) return Promise.reject('No user!')
return this.props.syncFirebaseToStore(user)
.then(thenCallThis)
.then(thenCallThis2)
})
}
Notes:
I've assumed that this.props.syncFirebaseToStore() returns a promise.
Wrapping the arrow function body in curlies kills the implicit return.
If your functions are already returning Promise instances - it's pretty easy to chain them up and attach an error handler, something like this:
componentDidMount = () => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
//Make sure that this call and the rest return Promises
this.props.syncFirebaseToStore(user)
.then(callThis1)
.then(callThis2)
.catch((err) => {console.error(err)})
}
}
Say you want to pass arguments down the chain so that previous call returns something used by the next one, then you would do something like this:
this.props.syncFirebaseToStore(user) //returns result1 when Promise resolved
.then((results1) => {return callThis1(results1)}) //consumes results1, returns results2
.then((results2) => {return callThis2(results2)})
.catch((err) => {console.error(err)})
with async await the above code could be written as such
componentDidMount() {
firebase.auth().onAuthStateChanged(async function(user) => {
if (user) {
await this.props.syncFirebaseToStore(user);
await thenCallThis()
await thenCallThis2()
}
}
}

Categories