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

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

Related

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.

Redux Action return undefined

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

React Redux - Wait until data is loaded

I'm using Redux (+ thunk) to fetch data from my API. I implemented a Data Fetcher component that calls all the actions and once done, dispatches a LOADED. In my actual main component where I render content, I wait until isLoaded flag in the props is set to true.
Here's the method in the data fetcher:
const fetchData = () => {
if (isAuthenticated) {
const p = [];
p.push(fetchDataSetOne());
p.push(fetchDataSetTwo());
Promise.all(p).then( () => setHasLoaded() );
}
}
Each of those fetch methods returns an axios promise, in which I dispatch once retrieved like so:
export const fetchDataSetOne = () => dispatch => {
return axios.get(`${API_URL}/dataSetOne`)
.then(res => {
dispatch({
type: FETCH_ALL_DATA_SET_ONE,
payload: res.data.docs
});
});
};
In my component's render function I only render the content when loaded (which is set by the LOADED dispatch from the setHasLoaded action) like so:
{ hasLoaded && <MyContent> }
Even though I "wait" for the actions to finish (= Promise.all), my variable hasLoaded is set to true before the fetched data is set. Can anybody help?
The problem is you return a function NOT a promise.
This resolves immediately.
See working code sample
export const fetchData2 = dispatch => () => {
dispatch({type: 'START_FETCH'})
const p = [
fetchDataSetOne(dispatch),
fetchDataSetTwo(dispatch)
];
Promise.all(p).then((res) => setHasLoaded(res));
};
// this returns a promise AFTER it calls an action
const fetchDataSetOne = dispatch => {
return axois.get(`${API_URL}/dataSetOne`).then(res => {
dispatch({
type: "FETCH_ALL_DATA_SET_ONE",
payload: res.data.docs
});
});
};
This resolves after both promises are resolved, but the state updates after each promise is resolved. To update state after all promises resolve, try this:
export const fetchData3 = dispatch => () => {
dispatch({ type: "START_FETCH" });
const p = [
axois.get(`${API_URL}/dataSetOne`),
axois.get(`${API_URL}/dataSetTwo`)
];
Promise.all(p).then(callActions(dispatch));
};
const callActions = dispatch => res => {
dispatch({
type: "FETCH_ALL_DATA_SET_ONE",
payload: res[0].data.docs
});
dispatch({
type: "FETCH_ALL_DATA_SET_TWO",
payload: res[1].data.docs
});
setHasLoaded(res);
};

Resources