Calling one async function inside another in redux-thunk - javascript

I'm building a react app and use redux-thunk for async operations. I have two functions getActivities() and createActivity() and I want to call the former after successful calling the latter. But if I put getActivities() inside then block of createActivity() it simply isn't get called (which is proved by not seeing console.log() which I put in getActivities()). Here are both functions:
export const getActivities = () => dispatch => {
console.log('again');
return axios.get(ENV.stravaAPI.athleteActivitiesBaseEndPoint, autHeaders)
.then(resp => {
dispatch({type: actions.GET_ACTIVITIES, activities: resp.data})
})
.catch(err => {
if(window.DEBUG)console.log(err);
})
};
export const createActivity = data => dispatch => {
dispatch(setLoadingElement('activityForm'));
return axios.post(URL, null, autHeaders)
.then(resp => {
if (resp.status === 201) {
dispatch(emptyModal());
}
// I WANT TO CALL getActivities() HERE
dispatch(unsetLoadingElement('activityForm'));
})
.catch(err => {
if(window.DEBUG) console.log(err.response.data.errors);
dispatch(unsetLoadingElement('activityForm'));
});
};
How can I call one inside another?

In order to call another action from inside one action creator you just need to just dispatch the action like dispatch(getActivities())
export const createActivity = data => dispatch => {
dispatch(setLoadingElement('activityForm'));
return axios.post(URL, null, autHeaders)
.then(resp => {
if (resp.status === 201) {
dispatch(emptyModal());
}
dispatch(getActivities());
dispatch(unsetLoadingElement('activityForm'));
})
.catch(err => {
if(window.DEBUG) console.log(err.response.data.errors);
dispatch(unsetLoadingElement('activityForm'));
});
};

getActivites()
This does sucessfully call getActivities(). However, it returns an anonymous function which contains the console.log() call. You ignore this returned value here.
You must dispatch the returned function in order to ensure it is called:
dispatch(getActivities())

Related

How to get response of a function in another file

I have two files named actions.js and vip.js. I have declared a function fetchVip in action file and imported it in vip file which will populate text element when screen is loaded. I want to access fetchVip response in vip file but i get undefined like this LOG useeffect undefined. While respone works as expected in action file.My code is below.
Vip File
import {fetchVip} from '../../Store/action';
const Vip = props => {
useEffect(() => {
console.log('useeffect', fetchVip());
}, [fetchVip()]);
action file
export const fetchVip = () => {
axios
.post(`${baseUrl}/get-vips`)
.then(function (response) {
return response.data;
})
.catch(function (error) {
console.log('error', error);
return {type: 'ERROR', payload: error};
});
};
fetchVip does not return anything, so you have to add a return statement here first:
export const fetchVip = () => {
return axios
.post(`${baseUrl}/get-vips`)
or remove the curly braces, then it will return as well.
export const fetchVip = () => axios
.post(`${baseUrl}/get-vips`)
...
return {type: 'ERROR', payload: error};
})
Now it will return a promise. That means that the result will not be there right away, but at some point later in time. Therefore, if you want to use it in the useEffect, you have to await for the result to arrive.
you could to this with the ES6 syntax:
useEffect(() => {
const getVip = async () => {
const vip = await fetchUsers();
console.log(vip)
//now you can do something with it
};
getVip();
}, [fetchVip]);
or the promise-then syntax:
useEffect(() => {
fetchVip().then(result => {
console.log(result);
//do something with the result
})
}, [fetchVip]);
This is wrong btw. remove the (). You want to check for the function here, not the result of the function.
}, [fetchVip()]);

Update a Table with React Hooks when a Row is Added, Deleted and Modified? [Issue: Gets Before Post and Delete]

I'm using Axios to get, put, and delete values from our database and have them displayed in a table; however, I need to refresh the page to see my changes. To find answers, I've visited these posts: How to update the page after call Axios Successful ? React, refresh table after action in react, and How can I use React Hooks to refresh a table when a row is deleted or added?
Unfortunately, I am still stuck and unsure how to dynamically update table rows upon response updates.
Update: I have noticed that the getValues function runs prior to the post and delete methods, which is why it is currently showing the previous values before the methods execute.
Axios.js - Where I am using get and delete methods. They work as I've had responses printed on the console.
import axios from "axios";
const getValues = async () => {
const values = await axios
.get("https://pokeapi.co/api/v2/type/")
.then((response) => {
return response.data;
})
.catch(function (error) {
console.log(error);
});
return values;
};
const postValues = (values) => {
axios
.post("https://pokeapi.co/api/v2/type/")
.then((response) => {
console.log("Post Values: ", response.data);
return response.data;
});
};
const deleteValues = (id) => {
console.log(id);
const deleteValues = axios
.delete(`https://pokeapi.co/api/v2/type/${id}`)
.then((response) => {
console.log("Delete Values: ", response);
})
.catch(function (error) {
console.log(error);
});
return deleteValues;
};
export { getValues, postValues, deleteValues }
ValuesTable.js - Where the delete method executes
import Axios from "./Axios";
const [data, setData] = React.useState();
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, [data]);
return (
{data.map((values) => {
<TableRow/>
<TableCell>{values.values}</TableCell>
<TableCell>
<Button
onClick={() =>
Axios.deleteValues(values.id);
}
/>
})};
)
Form.js - Where the post method executes
if (values.id === 0) {
Axios.postValues(values);
} else {
Axios.putValues(values, values.id);
}
UseState setData(result.data) loads all the existing values in the database.
Method deleteValues deletes a value in an array.
Method postValues adds a value into the database.
Well, you don't what to unconditionally call setData within an useEffect hook with data as a dependency as this will cause an infinite loop (render looping) to occur.
Since the getValues utility already unpacks the response.data value there is likely no need to do it again in your UI. Also, remove the data dependency.
useEffect(() => {
Axios.getValues()
.then((result) => {
setData(result.results);
});
}, []);
For the deleteValues utility, if console.log("Delete Values: ", response); is showing the correct values than I think you need to return this value from deleteValues.
const deleteValues = (id) => {
console.log(id);
const deleteValues = axios
.delete("https://pokeapi.co/api/v2/type/${id}`)
.then((response) => {
console.log("Delete Values: ", response);
return response; // <-- new data values
})
.catch(function (error) {
console.log(error);
});
return deleteValues;
};
Then in ValuesTable you need to update your data state with the new deleted values.
{data.map((values) => {
...
<Button
onClick={() => {
Axios.deleteValues(values.id)
.then(data => setData(data));
}}
/>
...
})};
Update
Ok, since the deleteValues utility doesn't return the updated data from the backend you will need to maintain your local state manually. I suggest doing this work in a callback handler. Upon successful deletion, update the local state.
const [data, setData] = React.useState();
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, []);
const deleteHandler = id => async () => {
try {
await Axios.deleteValues(id); // no error, assume success
setData(data => data.filter((item) => item.id !== id));
} catch(err) {
// whatever you want to do with error
}
};
return (
...
{data.map((values) => {
<TableRow/>
<TableCell>{values.values}</TableCell>
<TableCell>
<Button onClick={deleteHandler(values.id)}>
Delete
</Button>
})};
...
)
Note that I've written deleteHandler to be a curried function so you don't need an anonymous callback function for the button's onClick handler. It encloses the current id in an "instance" of the callback.
Update 2
If you are making a lot of different changes to your data in the backend it may just be easier to use a "fetch" state trigger to just refetch ("get") your data after each backend update. Anytime you make a call to update data in your DB, upon success trigger the fetch/refetch via a useEffect hook.
const [data, setData] = React.useState();
const [fetchData, setFetchData] = useState(true);
const triggerDataFetch = () => setFetchData(t => !t);
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data);
});
}, [fetchData]);
const deleteHandler = id => async () => {
try {
await Axios.deleteValues(id); // no error, assume success
triggerDataFetch(); // <-- trigger refetch
} catch(err) {
// whatever you want to do with error
}
};
I think you wrong in here :
useEffect(() => {
Axios.getValues().then((result) => {
setData(result.data); // result has no data property
});
}, [data]);
Please try change to this
useEffect(() => {
Axios.getValues().then((result) => {
console.log("RESULT",result); // check the actually response from API
setData(result.results); // the response of data is called results
});
}, [data]);
import axios from "axios"; const getValues = async () => { const values = await axios .get("https://pokeapi.co/api/v2/type/")
.then((response) => { return response.data; }) .catch(function (error)
{ console.log(error); }); return values; };
I don't know why but what are you trying to achieve with this. You should either use async/await clause or then clause but you are using both atleast have some good practice of coding first.
Second I think you should use async await inside try catch and remove then/catch phrases to make your code more understandable, then if you store your result inside values then simply return values.data and your problem might be resolved.
Since the deleteValues function deletes a specific object from the array on the server-side, I have decided to filter the list of objects in the array in order to remove the matching id to reflect on the front end. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
This is how I approached it.
{data.map((values) => {
...
<Button
onClick={() => {
setData(data.filter((item) => item.id !== values.id)); // <- Filter
Axios.deleteValues(values.id)
.then(data => setData(data));
}}
/>
...
})};

React how to call 1 promise after the previous promise is finish?

I have a React hook with this structure. What I want to do is, after finish calling getUserJoinedSlotList() and getting the result, then I want to call getAllAvailableSlot() both set the result into the useState hooks.
const [joinedSlotList, setJoinedSlotList] = useState(null)
const [availableSlotList, setAvailableSlotList] = useState(null)
const [isAllSlotLoading, setIsAllSlotLoading] = useState(true)
const getJoinedList = () => {
getUserJoinedSlotList()
.then(res => {
setIsLoading(false)
setJoinedSlotList(res.joined_slot)
})
.catch(error => {
setIsLoading(false)
setErrorMsg(error.message)
})
}
const getAvailableSlotList = () => {
getAllAvailableSlot()
.then(res => {
setIsAllSlotLoading(false) // this setting not working, at the 2nd API call
setAllAvailableSlotList(res.slot)
})
.catch(error => {
setAvailableErrMsg(error.message)
setIsAllSlotLoading(false)
})
}
useEffect(() => {
if (user !== null) {
getJoinedList()
}
}, [user])
Here is the code for getAvailableSlot(), I am using Aws amplify, so it actually return a promise for the GET request
import { API } from 'aws-amplify';
export const getAllAvailableSlot = async () => {
let path2 = path + '/list_all'
return API.get(apiName, path2)
}
What I tried:
Put in getAvailableSlotList as a callback function of getJoinedList(), like this:
const getJoinedList = (callback) => {
getUserJoinedSlotList()
.then(res => {
setIsLoading(false)
setJoinedSlotList(res.joined_slot)
})
.catch(error => {
setIsLoading(false)
setErrorMsg(error.message)
})
callback()
}
then
useEffect(() => {
if (user !== null) {
getJoinedList(getAvailableSlotList) // put in as call back here
}
}, [user])
By this, getAllAvailableSlot is called, I getting the result. But the result is not being set after calling setAvailableSlotList, and setIsAllSlotLoading(false) is not working as well, still true
Then I tried to call like this:
const getJoinedList = () => {
getUserJoinedSlotList()
.then(res => {
setIsLoading(false)
setJoinedSlotList(res.joined_slot)
getAvailableSlotList() // here call the function
})
.catch(error => {
setIsLoading(false)
setErrorMsg(error.message)
})
}
Again, is same result as above attempt.
Then I tried like this as well:
const calling = async () => {
await getJoinedList()
await getAvailableSlotList() //setAvailableSlotList and setAllIsLoading is not working, the 2ND CALL
}
useEffect(() => {
if (user !== null) {
//getJoinedList()
calling()
}
}, [user])
But still the getAvailableSlotList() set hooks is not taking any effect.
Specific problem:
I noticed that, the 2nd API calling is successful, but the follow up function which I call to setting the hooks, it just not taking any effect.
Means that:
Everything in getJoinedList() is working just fine. But when reach to getAvailableSlotList(), I can get the API result from it, but the setAvailableSlotList and setIsAllSlotLoading both cant set in the value
Question:
How to call another API after 1 API call is finished?
How to set react hooks at the 2nd API call?
Your second attempt should work. Here is a simplified sandbox example: https://codesandbox.io/s/romantic-bhaskara-odw6i?file=/src/App.js
A bit explanation on where the first and third attempts went wrong:
The first attempt is almost there, just that you need to move callback() inside the .then() block which essentially brings you to the second attempt.
The third one you used async/await but the problem is neither getJoinedList() nor getAvailableSlotList() returns a Promise so both requests will be sent around the same time without one waiting on the other to resolve first.
The simplest solution is actually to add your entire getAllAvailableSlot() function inside the getUserJoinedSlotList() through chaining. I see you're already using that, so I don't need to explain it in depth.
getUserJoinedSlotList().then(res => {
--- logic ---
getAllAvailableSlot().then(res2 => {
-- logic ---
}
}
Then chaining and its pairing could work here.
await getUserJoinedSlotList()
.then(res => /*assign hooks data*/)
.then(() => getAllAvailableSlot())
.then(availableSlot => /*assign availableSlot data*/)
.catch(e => console.log(e))

Using React.setState in componentDidMount() for data returned within nested promises?

I'm trying to put some data into state in a React app. The flow involves fetching a list of IDs from the HackerNews API, then taking each ID and making an additional API call to fetch the item associated with each ID. I ultimately want to have an array of 50 items in my component state (the resulting value of the each '2nd-level' fetch.
When I setState from JUST the single 'top-level' promise/API call, it works fine and my state is set with an array of IDs. When I include a second .then() API call and try to map over a series of subsequent API calls, my state gets set with unresolved Promises, then the fetch() calls are made.
I'm sure this a problem with my poor grasp on building appropriate async methods.
Can someone help me figure out what I'm doing wrong, and what the best practice for this is??
My component:
import React from 'react'
import { fetchStoryList } from '../utils/api'
export default class Stories extends React.Component {
state = {
storyType: 'top',
storyList: null,
error: null,
}
componentDidMount () {
let { storyType } = this.state
fetchStoryList(storyType)
.then((data) => {
console.log("data", data)
this.setState({ storyList: data })
})
.catch((error) => {
console.warn('Error fetching stories: ', error)
this.setState({
error: `There was an error fetching the stories.`
})
})
}
render() {
return (
<pre>{JSON.stringify(this.state.storyList)}</pre>
)
}
}
My API Interface:
// HackerNews API Interface
function fetchStoryIds (type = 'top') {
const endpoint = `https://hacker-news.firebaseio.com/v0/${type}stories.json`
return fetch(endpoint)
.then((res) => res.json())
.then((storyIds) => {
if(storyIds === null) {
throw new Error(`Cannot fetch ${type} story IDs`)
}
return storyIds
})
}
function fetchItemById(id) {
const endpoint = `https://hacker-news.firebaseio.com/v0/item/${id}.json`
return fetch(endpoint)
.then((res) => res.json())
.then((item) => item)
}
export function fetchStoryList (type) {
return fetchStoryIds(type)
.then((idList) => idList.slice(0,50))
.then((idList) => {
return idList.map((id) => {
return fetchItemById(id)
})
})
//ABOVE CODE WORKS WHEN I COMMENT OUT THE SECOND THEN STATEMENT
You are not waiting for some asynchronous code to "finish"
i.e.
.then((idList) => {
return idList.map((id) => {
return fetchItemById(id)
})
})
returns returns an array of promises that you are not waiting for
To fix, use Promise.all
(also cleaned up code removing redundancies)
function fetchStoryIds (type = 'top') {
const endpoint = `https://hacker-news.firebaseio.com/v0/${type}stories.json`;
return fetch(endpoint)
.then((res) => res.json())
.then((storyIds) => {
if(storyIds === null) {
throw new Error(`Cannot fetch ${type} story IDs`);
}
return storyIds;
});
}
function fetchItemById(id) {
const endpoint = `https://hacker-news.firebaseio.com/v0/item/${id}.json`
return fetch(endpoint)
.then(res => res.json());
}
export function fetchStoryList (type) {
return fetchStoryIds(type)
.then(idList => Promise.all(idList.slice(0,50).map(id => fetchItemById(id)));
}
One solution would be to update fetchStoryList() so that the final .then() returns a promise that is resolved after all promises in the mapped array (ie from idList.map(..)) are resolved.
This can be achieved with Promise.all(). Promise.all() take an array as an input, and will complete after all promises in the supplied array have successfully completed:
export function fetchStoryList(type) {
return fetchStoryIds(type)
.then((idList) => idList.slice(0,50))
.then((idList) => {
/* Pass array of promises from map to Promise.all() */
return Promise.all(idList.map((id) => {
return fetchItemById(id)
});
});
}

How to return a promise for a simple redux action creator?

I have a function add where no promises is returned to the caller.
For example:
let add = (foo) => {this.props.save(foo)};
And in another function of my application I would like to do:
...
return add()
.then( ... do something else here ... );
I just want to wait till add() is done and then do something else. I understand async save does not return a promise. Its a simple redux action.
export const save = (something) => (dispatch) => {
ApiUtil.patch(`google.com`, { data: {
something } }).
then(() => {
dispatch({ type: Constants.SET_FALSE });
},
() => {
dispatch({ type: Constants.SET_SAVE_FAILED,
payload: { saveFailed: true } });
});
};
I have pasted it here to show action
It looks to me like you are using redux-thunk for async actions, since your add action creator is in fact returning a function that accepts dispatch rather than a simple action object.
If you are using redux-thunk, then whatever you return from your thunk will be propagated back out. So if you modify your code such that:
let add = (foo) => this.props.save(foo); // Now returns result of save
Then update your thunk to:
export const add = (something) => (dispatch) =>
ApiUtil.patch(`google.com`, { data: { something } })
.then(() => { dispatch({ type: Constants.SET_FALSE }); },
() => { dispatch({ type: Constants.SET_SAVE_FAILED, payload: { saveFailed: true }});}
);
(Where you'll notice I removed the outside-most curlies), then the promise from ApiUtil should bubble all the way back up and you should be able to .then on it.
First you have to understand what this.props.save() returns. If it returns a promise, then you need only return that from add:
const add = (foo) => { return this.props.save(foo); }
So the problem is just what the definition of save is.

Categories

Resources