Im trying to chain multiple dispatch actions one for the other, by this order:
1.updateCart(dispatch,cartProducts ,loggedUser) - with redux-thunk.
after finishing fetching data, im dispatching actions by order:
2.dispatch(logoutReset()))
3.dispatch(logoutSuccess()))
4.then a refresh - window.location.reload())
but doesnt wait for dispatch to finish first before moving on so it keeps messing up.
i have tried many ways, with await or promises but didn't succeed.
i would like to learn from you guys, how to do it properly.
Component Navbar:
const handleLogout = async() => {
try{
await updateCart(dispatch,cartProducts ,loggedUser)
.then( ()=> dispatch(logoutReset()))
.then( ()=> dispatch(logoutSuccess()))
.then( ()=> window.location.reload());
}catch(err){
console.log(err)
}
};
Actions (updateCart) redux-thunk:
export const updateCart = async (dispatch, selectedProduct, loggedUser) => {
dispatch({ type: ActionTypes.UPDATE_CART });
try {
await userRequest.put(`carts/` + loggedUser.id, {
userId: loggedUser.id,
products: selectedProduct
})
} catch (error) {
console.log(error.response)
dispatch(returnErrors(error.response.data, error.response.status));
}
};
I have found a solution, posing if anyone will need it some day.
If you want to do something after dispatch finish, just put useEffect and the parameter that is updated after the dispatch, so after the dispatch and update you can do what you need to do.
very simple approach
const handleAddClick = () => {
dispatch(addToCart({selectedProduct:item,quantity}))
}
so after dispatch is finished cartProducts is updated.
useEffect(() => {
updateCart(dispatch,cartProducts ,loggedUser)
}, [cartProducts])
Related
I am currently trying to load my product data into redux, but so far I cant seem to pass the product information returned from firestore into the reducer.
Index.js -> load first 10 products from firestore soon after store was created.
store.dispatch(getAllProducts)
action/index.js
import shop from '../api/shop'
const receiveProducts = products => ({
type: types.RECEIVE_PRODUCTS
products
})
const getAllProducts = () => dispatch => {
shop.getProducts(products => {
dispatch(receiveProducts)
})
}
shop.js
import fetchProducts from './firebase/fetchProducts'
export default {
getProducts: (cb) => cb(fetchProducts())
}
fetchProducts.js
const fetchProducts = async() => {
const ProductList = await firebase_product.firestore()
.collection('store_products').limit(10)
ProductList.get().then((querySnapshot) => {
const tempDoc = querySnapshot.docs.map((doc) => {
return { id: doc.id, ...doc.data() }
})
}).catch(function (error) {
console.log('Error getting Documents: ', error)
})
}
In product reducers
const byId = (state={}, action) => {
case RECEIVE_PRODUCTS:
console.log(action); <- this should be products, but it is now promise due to aysnc function return?
}
I can get the documents with no issues (tempDocs gets the first 10 documents without any issue.) but I am not able to pass the data back into my redux. If I were creating normal react app, I would add a loading state when retrieving the documents from firestore, do I need to do something similar in redux as well ?
Sorry if the code seems messy at the moment.
fetchProducts is an async function so you need to wait for its result before calling dispatch. There are a few ways you could do this, you could give fetchProducts access to dispatch via a hook or passing dispatch to fetchProducts directly.
I don't quite understand the purpose of shop.js but you also could await fetchProducts and then pass the result of that into dispatch.
A generalized routine I use to accomplish exactly this:
const ListenGenerator = (sliceName, tableName, filterArray) => {
return () => {
//returns a listener function
try {
const unsubscribe = ListenCollectionGroupQuery(
tableName,
filterArray,
(listenResults) => {
store.dispatch(
genericReduxAction(sliceName, tableName, listenResults)
);
},
(err) => {
console.log(
err + ` ListenGenerator listener ${sliceName} ${tableName} err`
);
}
);
//The unsubscribe function to be returned includes clearing
// Redux entry
const unsubscriber = () => {
//effectively a closure
unsubscribe();
store.dispatch(genericReduxAction(sliceName, tableName, null));
};
return unsubscriber;
} catch (err) {
console.log(
`failed:ListenGenerator ${sliceName} ${tableName} err: ${err}`
);
}
};
};
The ListenCollectionGroupQuery does what it sounds like; it takes a tableName, an array of filter/.where() conditions, and data/err callbacks.
The genericReduxAction pretty much just concatenates the sliceName and TableName to create an action type (my reducers de-construct action types similarly). The point is you can put the dispatch into the datacallback.
Beyond this, you simply treat Redux as Redux - subscribe, get, etc just as if the data were completely local.
I am working on a web app that uses React + Redux, with a backend using Django (DRF). I am using axios to send in my API request, which is asynchronous. The issue I am facing right now is that the actions dispatched do not wait for the API call to finish before the next action is dispatched. Below is my code
const mapDispatchToProps = dispatch => ({
success: id => {
dispatch(fetchSalesProject(id));
dispatch(createMessage('Requirement successfully updated!'))
}
})
fetchSalesProject action (axiosInstance is just a custom modification of axios call, the functionality is the same)
export const fetchSalesProject = (id) => (dispatch) => {
console.log('enter sales project action')
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data')
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
createMessage action
export const createMessage = (message) => {
console.log('message')
return {
type: CREATE_MESSAGE,
message: message,
};
};
When calling this.props.success (refer to mapDispatchToProps), the message is displayed before the api call response data is received (evident by the fact that console.log('message') runs before console.log('fetched data'))
I would want the data to be fetched from the api call before i run the createMessage action, is there any advise on how to accomplish that? I am new to React and especially Redux, so hope that you guys can point me in the right direction on how to accomplish that.
Also, can I check whether it is wrong to have a dispatch in the mapDispatchToProps, and also a dispatch within the action (refer to fetchSalesProject action). Would it cause any issues with performance or is it frowned upon to do so? Please advise me as I am quite confused with Redux.
Thanks all for reading through, all help is appreciated :-)
while you are dispatching from UI, you just sending an object towards reducer which in his turn will modify the state at the store and in the end of the process will re-render components that refer to props that changed. At the moment you are dispatching the first action, there is nothing that tells the component that it should wait before sending the next object to the reducer
So you have 2 options,
the first is at UI itself use componentDidUpdate or useEffect for run the second action after the first action reduced
componentDidUpdate(prevProps) {
if (prevProps.salesProject != this.props.salesProject)
dispatch(createMessage('Requirement successfully updated!'))
}
while I assume that dispatch(fetchSalesProject(id)); modify salesProject prop
Another way to do that, and in case you actually fine with that message and salesProject will gonna get together to the reducer, is to dispatch them in one action
export const fetchSalesProjectAndMessage = (id, message) => (dispatch) => {
console.log('enter sales project action')
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data')
dispatch({
type: FETCH_SALES_PROJECT_AND_MESSAGE,
payload: { data: res.data, message }
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
and at reducer payload.data either payload.message will refer to desired info
There is a better way of doing this that does not force you combine two action creators. When your thunk action creator returns a promise then you can wait for it. Your current code did not return the axios promise but if you do return it you can do the following:\
const mapDispatchToProps = (dispatch) => ({
success: (id) => {
dispatch(fetchSalesProject(id)).then(() =>
dispatch(//wait for fetchSalesProject to finish
createMessage('Requirement successfully updated!')
)
);
},
});
export const fetchSalesProject = (id) => (dispatch) => {
console.log('enter sales project action');
return axiosInstance //you did not return anything here
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data');
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => {
dispatch(
returnErrors(err.response.data, err.response.status)
);
//return rejected promise here
return Promise.reject(err);
});
};
Main goal: unsubscribe correctly all firestore-listeners before logging out the user, preventing leaks.
Libraries involved: react, react-native, redux, redux-thunk and react-native-firebase.
Problem: Unsubscribe to firestore(...).onSnapshot() does not work when dispatch() is involved.
I fetch data with onSnapshot and returns the unsubscribe function to the caller component which I call on user logout. Strangely, UNSUBSCRIBE only works when no dispath is made...
I have a component (component.js) that is connected to redux store and fetch constantly some user data like this:
componentDidMount() {
this.unsubscribe = this.props.userFetch(); // userFetch is an action creator in actions.js
}
In actions.js
import firestore from '#react-native-firebase/firestore';
import auth from '#react-native-firebase/auth';
export const userFetch = () => {
return dispatch => {
const unsubscribe = firestore()
.doc(`users/${auth().currentUser.uid}`)
.onSnapshot({
error: e => console.warn('ERROR IN FETCH: ', e),
next: SnapshotUser => {
console.log('User: ', SnapshotUser.data());
// Will dispatch action below
},
});
return unsubscribe;
};
};
Note that there is no DISPATCH for the moment in the previous action creator.
If I call unsubscribe in component.js, the firestore onSnapshot listener gets unsubscribed correctly, like this:
onLogoutPressed = () => {
this.unsubscribe(); // <-- HERE it works (for the moment...)
auth()
.signOut()
.then(() => {
console.log('user has been signout');
})
.catch(error => {
console.log('Error: ',error);
});
};
Now if I want to send my fetched data to the redux store with a dispatch, I add the dispatch like this in actions.js
export const userFetch = () => {
return dispatch => {
const unsubscribe = firestore()
.doc(`users/${auth().currentUser.uid}`)
.onSnapshot({
error: e => console.warn('ERROR IN FETCH: ', e),
next: SnapshotUser => {
console.log('User: ', SnapshotUser.data());
// Will dispatch action below
dispatch({ // <--------------------------------- HERE
type: 'USER_FETCH_SUCCESS',
payload: SnapshotUser.data(),
});
},
});
return unsubscribe;
};
};
But then suddenly in my component.js, the this.unsubscribe doesn't work anymore on logout.
I've found that guy doing the same but works for him on React: here.
The solution provided by this other guy is basically the same too.
It looks like the firestore-onsnapshot-listener is wrapped in some dispatch call due to redux-thunk and I cant understand how it behaves now.
Does someone has any solution?
Ok solved it with the help of #ioss on Reactiflux.
The componentDidMount was mounted twice for some weird reasons, creating multiple listeners thus unmounting one was not enough.
Solved it by adding another run on unsubscribe() in componentWillUnmount().
I have a call to an API inside an action in redux.
export const registerUser = registeredUserData => async dispatch => {
let messages;
try {
const response = await axios.post('/users/register', registeredUserData);
messages = response.data
} catch (error) {
if (error.response) {
messages = error.response.data
}
}
dispatch({
type: REGISTER_USER,
messages,
});
};
This action is called when a form is sumbitted.
const onRegisterUser = (e) => {
e.preventDefault();
registerUser(registeredUserData);
};
When, if a call was successfull I want to redirect to another page.
The problem I'm facing is that I don't know how to implement history.push() in this case.
If I put it inside method of my component right after registerUser(registeredUserData); then it gets called right away no matter the response of the call. And I'm not sure if it is a good idea to redirect from the action itself.
All the help will be much appreciated.
In your example your action registerUser is a promise since it's an async function. So you could rewrite your onRegisterUser to look like this:
const onRegisterUser = (e) => {
e.preventDefault();
registerUser(registeredUserData)
.then(() => /* success */)
.catch(error => /* handle my failure */)
};
That being said you might want to consider creating SUCCESS and FAILURE actions for your network call. This allows you to potentially update the state of redux based on your register user api call.
You could modify your thunk to look like this:
export const registerUser = registeredUserData => async dispatch => {
try {
const response = await axios.post('/users/register', registeredUserData);
dispatch({
type: REGISTER_USER_SUCCESS,
messages: response.data,
});
} catch (error) {
if (error.response) {
dispatch({
type: REGISTER_USER_FAILURE,
messages: error.response.data,
});
}
}
};
You can then use one of React lifecycle methods to check for the state in redux change. Assuming the snippet is using react-redux and connect.
You might also want to consider looking into action creators.
An alternative to using React lifecycle methods is to use something like redux-saga which can signal on the SUCCESS and FAILURE actions and do the history.push on your behalf.
You might also want to look at react router if you haven't done so yet. It provides ways to access history and manage routing.
The point of async / await is to not have to use a nested promise chain in the case of your example.
Your try / catch block is equivalent to your then / catch. So if you want to use the above and have it catch when the response is a 400 you will either need to remove the try catch and handle the error in onRegisterUser, not recommended, or you will need to re-throw so that the catch is called when you call registerUser.
Here's an example on registerUser that should return a catch when failed response.
export const registerUser = registeredUserData => async dispatch => {
try {
const response = await axios.post('/users/register', registeredUserData);
await dispatch({
type: REGISTER_USER,
messages: response.data,
});
} catch (error) {
if (error.response) {
await dispatch({
type: REGISTER_USER,
messages: error.response.data,
isError: true,
});
throw new Error(error.response.data);
}
}
};
You might want to replace throw new Error(error.response.data) with something more specific by decorating the error object.
You are almost there. In your component, pass this.props.history as a parameter to the redux action. And from there, after the action is dispatched you can redirect to some other page.
P.S: It's not a bad idea to redirect from the action itself.
Inside your component:
const onRegisterUser = (e) => {
e.preventDefault();
registerUser(registeredUserData, this.props.history);
};
Inside your action:
export const registerUser = (registeredUserData, history) => async dispatch => {
let messages;
try {
const response = await axios.post('/users/register', registeredUserData);
messages = response.data
} catch (error) {
if (error.response) {
messages = error.response.data
}
}
dispatch({
type: REGISTER_USER,
messages,
});
history.push('/redirect-route);
};
Hi Allan,
You'll basically have this.props.history.push available from your Router which is passing it to all the Route components children in your app.
You can confirm this via console.log('__props__', this.props) in your render method for that component.
In order to implement this, I would suggest sending it as a callback to your action registerUser, in order to do this:
Add the cb to your action:
export const registerUser = registeredUserData => async dispatch => {
let messages;
try {
const response = await axios.post('/users/register', registeredUserData);
messages = response.data
} catch (error) {
if (error.response) {
messages = error.response.data
}
}
dispatch({
type: REGISTER_USER,
messages,
});
// maybe you want a conditional redirect?
if(/*somecondition to check data or error?*/){
cb && cb(); //if callback exists, invoke it
}
};
And for: onRegisterUser:
const onRegisterUser = (e) => {
e.preventDefault();
registerUser(registeredUserData, () => {
this.props.history.push(//route);
});
};
Hope that works, if it doesn't please describe the behavior.
I'm using React with Redux with multiple reducers.
I have a component in which I want to fetch data from multiple reducers but each time I make a call to action it re-renders the component (obviously...)
async componentDidMount() {
await this.props.getBooksNamesAsync();
await this.props.getAuthorsNamesAsync();
await this.props.getSubscribersAsync();
this.props.setFilter(
this.props.book.bookNames,
this.props.author.authorNames,
this.props.subscriber.subscriberNames
);
}
this.props.getBooksNamesAsync() is action on book.
this.props.getAuthorsNamesAsync() is action on author.
this.props.getSubscribersAsync() is action on subscriber.
my question is what the best practice for such issue ?
Is re-rendering the component every action is legitimate ?
Should I write another action that contains all these actions in one place ?
which is quiet code duplication and I prefer to avoid it...
or any other options...
The component rerenders every time there is state change... You can and you should... Here is an example from an old project:
First action creator:
export const fetchPosts = () => async (dispatch) => {
const response = await axios.get('/posts');
dispatch({ type: 'FETCH_POSTS', payload: response.data });
};
Second action creator:
export const fetchUser = id => async dispatch => {
const response = await axios.get(`/users/${id}`);
dispatch({ type: 'FETCH_USER', payload: response.data });
};
And both combined: (note, it's making use of lodash but you do not have to...)
export const fetchPostsAnUsers = () => async (dispatch, getState) => {
await dispatch(fetchPosts());
const userIds = uniq(map(getState().posts, 'userId'));
userIds.forEach(id => dispatch(fetchUser(id)));
};
This was a use case to cut down on the number of calls made to the api but the same holds true for your use case...