Redux Action return undefined - reactjs

So, I’m building an Expense App with React Native and Redux and I have this two actions:
export const getExpenses = () => async (dispatch) => {
await db.onSnapshot((querySnapshot) => {
const data = [];
querySnapshot.forEach((doc) => {
const { description, name, value, date, type } = doc.data();
data.push({
key: doc.id,
doc, // DocumentSnapshot
description,
date,
name,
value,
type,
});
});
dispatch({
type: TYPES.GET_EXPENSES,
payload: data,
});
dispatch({
type: TYPES.SET_LOADING_EXPENSES,
payload: false,
});
console.log('getExpenses', data);
});
};
export const filterMonthInfo = () => async (dispatch, getState) => {
const info = getState().monthExpenses.data; // data returned by getExpenses()
const currentMonth = getState().dateReducer.currentDate;
const filteredInfo = info
.filter(
(data) => moment(moment(data.date).format('DD/MM/YYYY')).format('MMMM YYYY') === currentMonth,
)
.sort((a, b) => new Date(b.date) - new Date(a.date));
dispatch({
type: TYPES.GET_FILTERED_EXPENSES,
payload: filteredInfo,
});
console.log('filtermonth', filteredInfo);
};
In the Screen where I want to use the data returned by filterMonthInfo i have the following useEffect:
useEffect(() => {
getExpenses();
filterMonthInfo();
getCurrentDate();
}, []);
But since getExpenses is an async function, filterMonthInfo will run first and is going to return undefined because this last function is filtered based on data returned by getExpenses.
What is the best approach so I can make getExpenses run first and then filterMonthInfo?
Thank you

If you want to run a code after an async call is finished, you have to wait for it using Promise. write the code as
useEffect(() => {
getExpenses()
.then(()=>{
filterMonthInfo();
getCurrentDate();
}
);
}, []);
or use async await as it makes syntax more clear

Related

how to make a dispatch wait for the first dispatch to run

i have 2 dispatch inside the useEffect, is there a way i can make the bottom dispatch to wait for the first one to run. i need to get the post._id from the first to pass to the second. please doesn't anyone have an idea on how to solve this?
useEffect(() => {
dispatch(getPost(params.slug));
dispatch(listComment(post._id));
}, [dispatch]);
// getPost action
export const getPost = (slug) => async(dispatch) => {
try {
dispatch({
type: types.POST_REQUEST
});
const {
data
} = await axios.get(`http://localhost:8080/api/posts/${slug}`);
dispatch({
type: types.POST_SUCCESS,
payload: data,
});
} catch (err) {
dispatch({
type: types.POST_FAIL,
payload: err.response && err.response.data.message ?
err.response.data.message :
err.message,
});
}
};
You can introduce a loading state for Post, and use that in another useEffect to achieve that
useEffect(() => {
dispatch(getPost(params.slug));
}, [dispatch]);
useEffect(() => {
if(!post.loading) {
dispatch(listComment(post._id));
}
}, [dispatch, post.loading]);
It's possible to coordinate this by observing loading flags with useEffect, but a simpler solution in my opinion is to extend the thunk to dispatch another action after the response for the post is available:
// actions:
export const listComments = (postId) => async (dispatch) => {
/* thunk that fetches comments on a post */
}
// Fetch post for slug, optionally with comments.
export const getPost = (slug, withComments = false) => async(dispatch) => {
dispatch({ type: types.POST_REQUEST });
try {
const { data } = await axios.get(`http://localhost:8080/api/posts/${slug}`);
dispatch({ type: types.POST_SUCCESS, payload: data });
if (withComments) {
dispatch(listComments(data.id));
}
} catch (err) {
const payload = err.response && err.response.data.message ?
err.response.data.message : err.message;
dispatch({ type: types.POST_FAIL, payload });
}
};
// In the component:
dispatch(getPost(slug, true));
you can try with below snippet, also will this post.id get after first dipatch or it will be present?
useEffect(() => {
dispatch(getPost(params.slug));
}, [required dispaencies]);
useEffect(() => {
if(!post._id || some loading){
dispatch(listComment(post._id));
}
},[required depencies])

Can an action be dispatched inside the THEN construct of a promise?

The following is a component that maps through the objects in a context variable and renders them.
const MyGroups = () => {
const { myGroups } = useContext(GlobalContext);
return (
<div className="my__groups">
<h1 className="my__groups__heading">My Groups</h1>
<div className="my__groups__underline"></div>
<div className="my__groups__grid__container">
{
myGroups.map(({id, data}) => (
<GroupCard
key={id}
name={data.name}
image={data.image}
/>
))
}
</div>
</div>
)
}
The following is my store function which I use to fetch my data from Firebase and dispatch an action to the Reducer.
function fetchGroupsFromDatabase(id) {
let myGroups = [];
db.collection("users").doc(id).get() // Fetch user details with given id
.then(doc => {
doc.data().groupIDs.map(groupID => { // Fetch all group IDs of the user
db.collection("groups").doc(groupID).get() // Fetch all the groups
.then(doc => {
myGroups.push({id: doc.id, data: doc.data()})
})
})
})
.then(() => {
const action = {
type: FETCH_GROUPS_FROM_DATABASE,
payload: myGroups
};
dispatch(action);
})
}
Now, the problem is that the "GroupCards" that I want to render, are not rendering although I can see in the console that the context variable get's populated after some time.
Working perfectly with setTimeout()
However, I have observed that instead of dispatching the action within the THEN construct, if I dispatch my action after some seconds by setTimeout, my component renders perfectly, like this:
function fetchGroupsFromDatabase(id) {
let myGroups = [];
db.collection("users").doc(id).get() // Fetch user details with given id
.then(doc => {
doc.data().groupIDs.map(groupID => { // Fetch all group IDs of the user
db.collection("groups").doc(groupID).get() // Fetch all the groups
.then(doc => {
myGroups.push({id: doc.id, data: doc.data()})
})
})
})
setTimeout(() => {
const action = {
type: FETCH_GROUPS_FROM_DATABASE,
payload: myGroups
};
dispatch(action);
}, 3000);
}
I humbly request you to kindly spare some of your valuable time and provide some solution to my problem.
Thank You very much.
In order to wait for all of the groups .get() calls to resolve, and to dispatch with the results, use Promise.all:
async function fetchGroupsFromDatabase(id) {
const doc = await db.collection("users").doc(id).get() // Fetch user details with given id
const myGroups = await Promise.all(
doc.data().groupIDs.map(groupID => // Fetch all group IDs of the user
db.collection("groups").doc(groupID).get() // Fetch all the groups
.then(doc => ({ id: doc.id, data: doc.data() }))
)
);
const action = {
type: FETCH_GROUPS_FROM_DATABASE,
payload: myGroups
};
dispatch(action);
}

Update state with Object using React Hooks

I'm getting data from Firebase and want to update state:
const [allProfile, setAllProfile] = useState([]);
.....
const displayProfileList = async () => {
try {
await profile
.get()
.then(querySnapshot => {
querySnapshot.docs.map(doc => {
const documentId = doc.id;
const nProfile = { id: documentId, doc: doc.data()}
console.log(nProfile);//nProfile contains data
setAllProfile([...allProfile, nProfile]);
console.log(allProfile); // is empty
}
);
})
} catch (error) {
console.log('xxx', error);
}
}
The setAllProfile will update the state when the iteration is done. So in order for your code to work, you will need to pass the callback function to the setAllProfile as shown in the docs
setAllProfile((prevState) => [...prevState, nProfile])
UPDATE
Example demonstrating this at work
Since setAllProfile is the asynchronous method, you can't get the updated value immediately after setAllProfile. You should get it inside useEffect with adding a allProfile dependency.
setAllProfile([...allProfile, nProfile]);
console.log(allProfile); // Old `allProfile` value will be printed, which is the initial empty array.
useEffect(() => {
console.log(allProfile);
}, [allProfile]);
UPDATE
const [allProfile, setAllProfile] = useState([]);
.....
const displayProfileList = async () => {
try {
await profile
.get()
.then(querySnapshot => {
const profiles = [];
querySnapshot.docs.map(doc => {
const documentId = doc.id;
const nProfile = { id: documentId, doc: doc.data()}
console.log(nProfile);//nProfile contains data
profiles.push(nProfile);
}
);
setAllProfile([...allProfile, ...profiles]);
})
} catch (error) {
console.log('xxx', error);
}
}
You are calling setState inside a map and therefore create few async calls, all referred to by current ..allProfile value call (and not prev => [...prev...)
Try
let arr=[]
querySnapshot.docs.map(doc => {
arr.push({ id: doc.id, doc: doc.data() })
}
setAllProfile(prev=>[...prev, ...arr])
I don't sure how the architecture of fetching the posts implemented (in terms of pagination and so on, so you might don't need to destruct ...prev

The function call inside the redux action doesnot get executed

I'm trying my first react application with redux, along with Thunk as middle ware. When calling the action from one of the components, the action is hit but the code inside the action return is not executed. I have no clue what I'm missing here. I'm also using Firestore to get the data.
export const getBikeTypes = () => {
console.log('outside return')
return (dispatch, getState, { getFireBase, getFireStore }) => {
console.log('inside return')
const firestore = getFireStore();
firestore.collection('BikeTypes').get()
.then((response) => {
console.log(response)
return response
}).then(() => {
dispatch({ type: 'GET_BIKETYPES' });
}).catch((err) => {
dispatch({ type: 'GET_BIKETYPES_FAIL', err });
})
}
};
I think you should dispatch action with the payload once you get the response.
const firestore = getFireStore();
firestore.collection('BikeTypes').get()
.then((response) => {
dispatch({ type: 'GET_BIKETYPES', payload: response })
})

React Redux Call action within action and get return value

I am trying to call and action from an action to get a database record by name, then I want to use the ID of the role record in the SignUp action that is currently being called .
How can I reuse the code for my GetRolebyName action from within the sign up action, I was trying to avoid doing the same APi request in two places.
Essentially I am just trying to look up the RoleId when creating a user.
Role actions:
export const fetchRoleByName = name => async dispatch => {
const response = await db.get(`/roles?name=${name}`);
dispatch({
type: FETCH_ROLE,
payload: response.data[0]
});
};
Sign up Action:
export const signUp = values => async (dispatch, getState) => {
const role = await dispatch(fetchRoleByName(values.userType))
const response = await db.post('/users/',
{
...values,
roleId: role.id
}
);
dispatch({
type: SIGN_UP,
payload: response.data
});
history.push('/');
};
Solution:
As pointed out by Kaca992, the fetchRoleByName action never actually returned anything so the change required was as per below;
export const fetchRoleByName = name => async dispatch => {
const response = await db.get(`/roles?name=${name}`);
const data = response && response.data && response.data[0];
dispatch({
type: FETCH_ROLE,
payload: data
});
return data;
};
Inside fetchRoleByName just return response. Return from dispatch is the return value of the inner function:
export const fetchRoleByName = name => async dispatch => {
const response = await db.get(`/roles?name=${name}`);
dispatch({
type: FETCH_ROLE,
payload: response.data[0]
});
return response; (or return response.data[0] if that is the role object you want, but then I would recommend writing it like this: response && response.data && response.data[0] just in case of hitting an unexisting value from db)
};
Hope this helps.

Resources