I'm working on a React Native with Expo project, right now i'm trying to delete an item from a list inside a local database. Problem is in the action where I send it. Here is my code.
export const eliminatePlace = (placeId) => {
console.log(`Inside the action with placeID ${placeId}`);
return async dispatch => {
console.log('returned');
try {
const dbResult = await deletePlace(placeId);
console.log(dbResult);
dispatch({
type: DELETE_PLACE,
id: placeId
});
} catch (err) {
throw err;
console.log(err);
}
};
}
Somehow the console.log inside the return didn't fire up, my workaraound was this:
export const eliminatePlace = async (placeId, dispatch) => {
try {
console.log(`Trying to eliminate place with ID ${placeId}`);
const dbResult = await deletePlace(placeId);
console.log(dbResult);
dispatch({type: DELETE_PLACE, id: placeId});
} catch (err) {
throw err;
console.log(err);
}
};
Then it did work, but this is not the best practice, any ideas on why the correct way didn't work?
Here is the link to my github repo where you can download the project:
https://github.com/josmontes/rn-places
In case someone needs to see another place of the code please ask, I didn't add anything else so it doesn't bloats the question and because the problem is inside this function.
You shouldn't be calling async functions inside your action creators. You can read more about why here. Instead, you should use async actions. Even though you aren't making an API call, you can still represent your process as request-success-failure. Basically, dispatch a "request" action and as a side effect call your async function. Once it resolves, you dispatch either "success" or "failure" action, depending on the result. You can then put the results from the database in the payload of the "success" action. You can read more about that here.
I believe the the second example you gave works, because that's basically just the "success" action. It only dispatches a regular action once the async function resolves, while in the first example, the action function itself is async, which redux doesn't like.
Related
I'm reading the docs on Transaction operations, and I figured the t.set() method would work similar to the docReference.set() documented in the the Add data page.
To my surprise, it doesn't:
const newCustomerRef = db.collection('customers').doc();
await db.runTransaction(t => {
const res = t.set(newCustomerRef, formData)
console.log(res)
});
The res object above (return value of t.set()) contains a bunch of props that looks obfuscated, and it doesn't look as if it's intended for you to work with them.
Is there any way to get the ID of the newly created document within a Transaction?
Update
What I'm trying to achieve is to have multiple data operations in 1 go, and have everything reverted back if it fails.
As per Doug answer, if newCustomerRef already contains the ID, it seems what I am missing is to delete it during the catch block in case the transaction fails:
try {
const newCustomerRef = db.collection('customers').doc();
await db.runTransaction(t => {
const res = t.set(newCustomerRef, formData)
console.log(res)
});
} catch (e) {
newCustomerRef.delete()
//...error handling...
}
This is sort of a manual thing to do, feels a little hacky. Is there a way to delete it automatically if the transaction fails?
newCustomerRef already contains the ID. It was generated randomly on the client as soon as doc() was called, before the transaction ever started.
const id = newCustomerRef.id
If a transaction fails for any reason, the database is unchanged.
The operation to add the document is performed in the set(..) call. This means by using set() on the transaction, everything is rolled back should the transaction fail.
This means in the following example
...
await db.runTransaction(t => {
t.set(newCustomerRef, formData)
... do something ...
if (someThingWentWrong) {
throw 'some error'
}
});
Should someThingWentWrong be true, no document will have been added.
I have the following two functions:
async function queryData(){
const query= await axios.get('...')
const queryStatus = portNames.map(...);
const dataStatus= await Promise.all(queryStatus);
return dataStatus;
}
export function actionData(){
const data = queryData();
return{
type:cst.RECEIVE_DATA,
payload:data
}
}
queryData() function return after some line code some data in promise...in the second function i put the data in payload for sending my action to reducer.
the problem is that when i'm trying to pass my data from first function in two second, if i output my variable in console.log() inside the second function,it shows:
instead if i try to print my variable inside the first function
i'm able to access my value from promise...what could be the problem that might create promise pending in actionData()?..therfore how can i pass my data value from promise to action in a way to dispatch my action with relative data to reducer?
Asynchronous functions always return promises. If you want to get access to the value they contain, you either need to call .then on the promise, or put your code in an async function and await the promise.
Since you are using redux, there are some additional considerations. By default, redux does everything synchronously. Dispatching an action should synchronously go through the reducers and update the state. To do async things with redux you'll need to add a middleware. There are a few possible async middlewares, but the one recommended by the redux team is redux-thunk.
With redux-thunk in your project, instead of dispatching an action object, you can dispatch a function. That function is empowered to do async things and dispatch actions when it's done. So a possible implementation for your case would be something like this:
function actionData() {
return async function(dispatch) {
const data = await queryData();
dispatch({
type: cst.RECEIVE_DATA,
payload: data
});
}
}
Let me explain to you the flow here.
First we come here
const data = queryData();
In queryData function we have:
async function queryData(){
const query= await axios.get('...') <------------ first breakpoint ----------------
// ......
}
When this async req is hit, the code doesn't stop it's flow, it continues. Next return statement is hit.
return {
type:cst.RECEIVE_DATA,
payload:data // data is undefined at this moment
}
In the meantime the async request's response comes back. We sequentially execute the next 3 statements of queryData() function
const queryStatus = portNames.map(...);
const dataStatus= await Promise.all(queryStatus);
return dataStatus;
And now the data variable gets updated.
To ensure proper flow, you could write something like this:
export async function actionData() {
const data = await queryData();
return {
type:cst.RECEIVE_DATA,
payload:data
}
}
I'd encourage to you read about async/await on MDN.
I had this executeOrder function in my child component. this was directly calling the API to get data from backend in this way
const executeOrder = async (data, actions) => {
value = await getToken(
props.orderTotal
);
return value.ecToken;
};
As you can see, i was using await function in my executeOrder. With this, it would wait for the execution to complete and then provide me a return value.ecToken
Since i am using REACT, i had to make use of action dispatcher and make a API call from my Redux saga file. So i created an action dispatcher and made a API call this way.
const executeOrder = (data, actions) => {
props.initiateIdentity(props.orderTotal); //ACTION DISPATCHER
console.log("2");
return props.ecToken;
};
Now how do i ensure this action dispatcher completes it execution ? Because, i am dispatching the action and making a API call. The response of the API call, i am putting it in a state and passing that state value from main component to child component via mapStateToProps
By the time props.initiateIdentity(props.orderTotal) is getting executed, it goes to next line return props.ecToen and since this value is still not provided back from API call, i get a blank value.
How do i ensure this action dispatcher has finished its task ?? Someone please help me
PS- here is my SAGA
function* initiateIdentity(action: TInitiateIdentPayPal) {
try {
const resp = yield call(
getToken,
orderTotal,
);
console.log("1");
yield put(savePiBlob(resp));
} catch (err) {
} finally {
}
}
In your case, You have to create 3 status constant of a actions and you get the response in function* in saga file. Your saga function should be look like:
function* callApiInSaga(actions) {
try {
const data = yield callApiByAxiosOrFetch({ type: actions.data });
yield put({
type: constants.CALL_API_SUCCESS,
data,
}); `// if your function calling api is executed, and you get response from server, put({}) in saga will have you transfer data from saga to reducer. and you can use data via mapDispatchToProps:`
} catch (error) {
yield put({
type: constants.CALL_API_FAILED,
});
}
}
Sorry, you cannot achieve it that way.
In the world of Redux, do mutation(by dispatching actions) and get the latest state are two sorts of operations and never mixed out. redux will resolve the updated state somehow, but cannot guarantee the exact timeslot.
If you store ecToken in your redux store then you don't need to return its value from executeOrder. Instead when you call savePiBlob(resp) you can update ecToken in your reducer. The parent can access ecToken with mapStateToProps.
Or if you do want to return ecToken from the child component then use componentDidUpdate to check if ecToken is updated and then return ecToken by calling a parent method.
componentDidUpdate(prevProps) {
if (this.props.ecToken !==null && this.props.ecToken !== prevProps.ecToken)
{
//call parent method here
}
}
My if condition might not be accurate. you can add whatever works for you
I have two actions on button change function which are dependent on one another. What I want to do is I want to put these two function in async/await structure so that after update_other_filter action ends, that I will be able to run getTotalData action. Running it like below structure actually does not update state in correct way. I am sending previous state(before update_other_filter) in getTotaldata.
You guys will probably say I have to dispatch getTotalData inside update_other_filter action when it resolves. But in this state of my project it seems I can not change anything. I am not really good with async/await and promises concept so, I only want to create async/ await fucntion inside my react component than I want to call it inside onChange function. Is there a way to do that?
onChange = {(event) => {
this.props.setSpinner()
//this update filter function updates filter which will be sent to server in getTotalData action
this.props.update_other_filter(true,"website",!event.target.checked)
//this action should wait for update_other_filter to end than it has correct parameters to send to server
this.props.getTotalData(this.props.totalFilters, apiUrl)
}
async onChange = {(event) => {
this.props.setSpinner()
await this.props.update_other_filter(true,"website",!event.target.checked)
this.props.getTotalData(this.props.totalFilters, apiUrl)
}
// I will make function wait that needs for dependent function and also add some error handling.
async onChange = {(event) => {
this.props.setSpinner()
try
{
await this.props.update_other_filter(true,"website",!event.target.checked)
this.props.getTotalData(this.props.totalFilters, apiUrl)
}
catch(e)
{
thorw e;
}
}
Have some Firestore .set() functions that are suddenly not working-- they are not doing anything in the then statement and no code after "await" is executing with them if formatted as async/await. No docs are being added to Firestore.
First had this from the Firestore docs:
export function addProfileToDB(advisor, uid, state) {
firebaseDB.collection("users").doc(uid).set(profile)
.then(function(docRef) {
console.log("User added with ID: ", docRef.id) // never fires
})
.catch(function(error) {
console.error(error) // never fires
})
}
Tried a la this answer:
export async function addProfileToDB(profile, uid, state) {
const docRef = firebaseDB.collection("users").doc(uid)
console.log(docRef) // returns correctly
await docRef.set(profile)
console.log('added') // never happens
}
Using add() and read functions in the same setup works fine, but I need to specify the doc id (uid) in this case. Thoughts?
You could wrap the async/await version with a try catch block.
Same for the code which calls addProfileToDB (and you should await it too).
Maybe you'll see something.
If it's a cloud function that trigger addProfileToDB, did you watch logs on the firebase console ?
This ended up being due to a sign out user function that was being called at the same time as the addProfileToDB. Although the user being signed in wasn't required for addProfileToDB, for some reason the interruption caused the set to not go through. I moved the sign out user function to fire later and all is now working as expected. Thank you all for helping!
If set() throw an error, console.log('added') can't be ritched.
My solution consist to catch if there is any error.
Solution
export async function addProfileToDB(profile, uid, state) {
const docRef = firebaseDB.collection("users").doc(uid)
console.log(docRef) // returns correctly
try {
await docRef.set(profile)
console.log('added')
} catch (e) {
console.log(e)
}
}