Using asynchronous await on dispatch - reactjs

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

Related

Preventing a setState from running until network request is complete

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)

Axios calls with React : best practises

i want to know if there is some clean code or update to make it on my code, because i think i repeat the same code on every actions on my redux, my question is how can I avoid calling axios on my actions files ?
Please take a look on my code here :
export const SignInType = (host, lang) => async (dispatch) => {
try {
dispatch({
type: USER_LOGIN_SIGNINTYPE_REQUEST,
});
const { data } = await axios.get(
`/${lang}/data?host=${host}`
);
console.log({ data });
dispatch({
type: USER_LOGIN_SIGNINTYPE_SUCCESS,
payload: data,
});
dispatch({
type: USER_LOGIN_CLEAR_ERROR,
});
} catch (err) {
dispatch({
type: USER_LOGIN_SIGNINTYPE_FAIL,
payload: err,
});
}
};
I Really want to delete the Axios name from my actions file and make it on a separate file, but how can i do this ?
Thank you
We can suggest but there's no correct answer to this, initially any redundant lines of code can be abstracted, so in order to make things a little bit easier, we need to abstract the obvious and add the meaningful, e.g:
abstract the way you write action creators:
const actionComposer = (options) => (...args) => async dispatch => {
const modifiedDispatch = (type, payload) => dispatch({ type, payload });
const { action, onSuccess, onFailed } = options(modifiedDispatch);
try {
if (action) {
const res = await action(...args)
onSuccess(res);
}
} catch (err) {
onFailed(err)
}
}
then your code can look like this:
export const SignInType = actionComposer((dispatch)=> {
return {
action: async (host, lang) => {
dispatch(USER_LOGIN_SIGNINTYPE_REQUEST);
const { data } = await axios.get(`/${lang}/data?host=${host}`);
return data;
},
onSuccess: (res) => {
dispatch(USER_LOGIN_SIGNINTYPE_SUCCESS, data);
dispatch(USER_LOGIN_CLEAR_ERROR);
},
onFailed: (err) => {
dispatch(USER_LOGIN_CLEAR_ERROR, err.message)
}
}
})
Redux Toolkit already has a createAsyncThunk API that does all the work of defining the action types and dispatching them for you. You should use that.
Alternately, you can use our RTK Query data fetching and caching library, which will eliminate the need to write any data fetching logic yourself.

Axios Delete doest not work in react-redux app

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

How to redirect from component after action was called successfully in React?

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.

How to implement redux thunk actions with offline functionality?

For a project i need to implement some kind of offline redux-action queue.
And i'm using redux-thunk for async actions. But i saw that libraries like redux-offline can't handle async actions with redux thunk yet.
Does anyone have an idea how i can best tackle this?
My actions look like this:
export function fetchIdeasForOrganisationAction(organisation) {
const options = {
credentials: 'include',
};
return async (dispatch) => {
try {
const response = await fetch(`http://${SERVER_URL}:${SERVER_PORT}/organisations/${organisation}/shared/ideas`, options);
if (!response.ok) throw Error();
const ideas = await response.json();
dispatch(newIdeasFetched(ideas));
} catch (e) {
dispatch(noIdeasFoundAction('No ideas found '));
}
};
}
function newIdeasFetched(ideas) {
return { type: "NEW_IDEAS_FETCHED", value: ideas };
}
function noIdeasFoundAction(errorMessage) {
return { type: 'NO_IDEAS_FOUND', errorMessage };
}

Resources