export const deleteComment = (id) => {
console.log("id", id);
return async (dispatch) => {
try {
const response = await axios.delete(
`http://localhost:5000/comments/${id}`
);
console.log("response", response);
dispatch(actions.DeleteCommentAction(id));
} catch (err) {
console.log(err);
}
};
};
For some reasons, this code does not work. When i use this acton the console.log("id",id) is being executed, but console.log with response is not. I tested my route for deleting in Postman and everything works, does anyone know what is this happening?
Route for deleting, but as i said. It work, it some issue in React i guess:
router.delete("/:id", async (req, res) => {
const id = req.params.id;
try {
Comment.findByIdAndDelete(id, function (error, response) {
if (error) {
return res.send(error);
}
console.log(response);
return res.send(response);
});
} catch (error) {
return res.send(error);
}
});
Have you inspected the network tab to see if the request was sent?
Can you share the component where you are invoking deleteComment?
And perhaps try to simplify and execute the deletion outside the deleteComment method, it might be you are not injecting the dispatch method into the anonymous function.
The cause could also related to redux. I guess you are not using a redux middleware to handle the asynchronous executions as you are waiting the deletion, which could be a possible scenario for the issue. And maybe you could consider adding one of those middlewares like reduxk-thunk or redux-saga for example.
I actually forgot to add:
const mapDispatchToProps = {
deleteComment,
};
Related
I have the following function, inside of a Context file in my React app:
const fetchAll = (userId) => {
try {
fetchDetails(userId)
// To be clear... There's multiple functions here, i.e:
// fetchContact(userId)
} catch (err) {
console.log(err)
}
setPending(false)
}
I've removed some of the functions - but the main premise of the function is to combine multiple promises together, and until these are complete, display a 'pending' component.
This pending component is displayed if the 'pending' state is set to true:
const [pending, setPending] = useState(true)
However, at the moment, what is happening is that the try is attempted, but the setPending is executed at the same time.
I thought one way around this would be to utilise a 'finally' call at the end of the my try / catch, but that still executes at the same time. Like this:
const fetchAll = (userId) => {
try {
fetchDetails(userId)
} catch (err) {
console.log(err)
} finally {
setPending(false)
}
}
I don't want any of my functions to be run asynchronously: I want them all to execute at the same time to prevent a waterfall effect of multiple network requests at once.
For reference, my individual 'fetch' functions call an endpoint and set state data based upon the response:
const fetchDetails = (userId) => {
axios.post("/api/fetch/fetchDetails", {
id: userId
})
.then((response) => {
console.log(response.data)
setName(response.data.name)
setPreviewPhoto(response.data.profile_picture_url)
setPhotoName(response.data.profile_picture_name)
setPhotoFile(response.data.profile_picture_url)
})
}
Does anyone have any suggestions as to how I could make this work?
Let's assume you have 2 API calls: fetchAll('123') and fetchAll('321');
In order to wait for all of your requests and then update your state, you should use Promise.all like this:
Promise.all([fetchAll('123'), fetchAll('321')]).then((responses) => {
setPending(false)
}
fetchDetails returning a promise, you need to use async/await
const fetchAll = async (userId) => {
try {
await fetchDetails(userId)
} catch (err) {
console.log(err)
} finally {
setPending(false)
}
}
You can have multiple async calls using Promise.all() or Promise.allSettled() depending on your use case.
setPending(true)
try {
await Promise.all([() => fetchAll(1), () => fetchAll(2)])
} finally {
setPending(false)
}
This will wait for all calls to complete (or one to fail)
I tried to put POST by AXIOS function inside another function to reuse it multiple times. but unfortunately It doesn't work as I expected (which was working using FETCH),
AXIOS CALL JS:
export async function post(apiRoute, body) {
try {
body = JSON.stringify(body);
axios.post(apiRoute, body, httpOptions)
.then((res)=>{
console.log(res.data); // I have data here
return res.data;
});
}
catch (err) {
console.log(err);
return err;
}
}
And the caller is:
async DoLogin(userName, password) {
var data = await post(url,{ // post is axios method defined above in another file
UserName: userName,
Password: password
});
return data; // I have nothing here
}
And the problem is I got undefined in DoLogin but got data inside POST method, It seems the problem is related to timing, axions returns a promise which is not possible to read in DoLogin, how I can do this? I did this via FETCH and it works perfectly.
try using async-await in this way.
export async function post(apiRoute, body) {
try {
const response = await axios.post(apiRoute, body, httpOptions)
return response.data;
}
catch (err) {
return err;
}
}
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 have a thunk using Axios that's posting to an Express route using Sequelize.
The route is posting correctly (ie. data is getting added to the db) but the action inside of the React component isn't behaving as expected. Using async/await, I expect the action to wait until it completes the db post before continuing but that's not the case here. I'm getting undefined from the action.
The thunk hits the express route where I'm dispatching the action to update my redux store and returning the response:
const addedNewList = (newList) => ({type: ADD_NEW_LIST, newList})
export const addNewList = (name, userId) => async dispatch => {
try {
const { data } = await axios.post('/api/list/add', { name, userId })
dispatch(addedNewList(data))
return data
} catch (err) {
console.error(err)
}
}
Using debugger, I can confirm that return data is in fact returning the response from the server that I need. I can also confirm that the redux store is getting updated correctly.
But here, when I try and access that response data as result, I get undefined:
handleSubmit = async () => {
const result = await this.props.addNewList(this.state.name, this.props.userId)
// ** result is 'undefined' **
this.handleClose()
// pass off the results
}
If I add a setTimeout after I evoke the addNewList action, it works as expected. This suggests to me that maybe it's not returning a promise? But my understanding was that if you returned the response from the server in the thunk, it would do that.
For completeness, here is my route which I've also confirmed with debugger that data is being passed as expected:
const userAuth = function(req, res, next) {
if (req.isAuthenticated()) {
return next()
}
res.status(401).send('Unauthorized user')
}
router.post('/add', userAuth, async (req, res, next) => {
const { name, userId } = req.body
try {
const list = await List.create({ name, userId })
res.json(list)
} catch(err) { next(err) }
})
Why is the action returning undefined in the handleSubmit method?
Try returning the dispatch of addedNewList(data) instead:
export const addNewList = (name, userId) => async dispatch => {
try {
const { data } = await axios.post('/api/list/add', { name, userId })
return Promise.resolve(dispatch(addedNewList(data)));
} catch (err) {
console.error(err)
}
}
That being said, you could consider restructuring the component to instead utilize mapStateToProps to use values/result from the updated Redux store rather than explicitly awaiting the response and manually passing the value?
The response from Alexander got me on the right track so I'm sharing my solution in case it helps someone (as he suggested).
While I could have continued to try and solve this by wrapping the dispatch in a Promise, the better solution was to rethink how the component was structured.
In my situation, I wanted to get the ID for the newly created row in the database so that I could pass it into history.push.
handleSubmit = async () => {
const result = await this.props.addNewList(this.state.name, this.props.userId)
this.handleClose()
history.push(`/list/${result.id}`)
}
With result coming back undefined, the url was not updating correctly.
The better solution was to access the new data from the redux store where it was updated. This way I could be certain the history wouldn't get updated until the data was ready.
So my updated component now looked something like this where the history wouldn't update until a newId was available:
handleSubmit = () => {
this.props.addNewList(this.state.name, this.props.userId)
this.handleClose()
}
render(){
const { newId } = this.props
if (newId) {
history.push(`/list/${newId}`)
}
return (
....
)
}
}
const mapStateToProps = (state) => {
return {
newId: state.list.newId
}
}
Instead of putting this into render, I could probably also use a component lifecylcle method like componentWillReceiveProps or similar.
I was tinkering around with async await in react-native by making the dispatch asynchronous. Is there anything wrong or problematic with making the dispatch asynchronous as shown below? It seems to work (other than it's a polyfill). If there's nothing wrong with this approach, I'm thinking it could work quite nice for react-native.
export function fetchData(date, lng, lat){
const {year, month} = date;
return async(dispatch) => {
dispatch(requestData())
try {
const data = await request(`http://data.police.uk/api/crimes-street/all-crime?lat=${lat}&lng=${lng}&date=${year}-${month}`)
dispatch(recieveData(data))
} catch (err) {
dispatch(requestError(err))
}
}
}
That should work quite well (assuming you have redux-thunk in your middleware). I use a similar approach (with promises rather than async/await) in several React/Redux apps.
There is a redux-promise as well, but I find it insufficient for two reasons: there's no "begin" action, and there's no way to set meta which is often necessary for asynchronous actions. So I prefer to dispatch the actions explicitly rather than the promises, as your code does.
I use this pattern :
const result = (res) => {
if (!res.result) {
this.setState({
...this.state,
Valid: false
});
}
};
AsyncFn(this.props.dispatch, field , result);
export async function AsyncFn(dispatch, field, Callback) {
try {
const response = await (
request.post(url)
);
if (response && response.body) {
if (response.body.result) {
dispatch(Auth());
dispatch(openModal());
} else {
Callback(response.body);
}
} else {
Callback({ result: false, message: 'Not valid' });
}
} catch (err) {
Callback({ result: false, message: err.message });
}
}