react redux promise reject making mutiple api call - reactjs

I am using react-redux in my application. It communicates with backend API. When there is any error it should dispaly an alert message.
Follwing is my action:
function getByStatus(code) {
return dispatch => {
StoreService.getByStatus(code)
.then(
users => {
......
}
).catch(error => {
dispatch(failure(error));
dispatch(alertActions.error(error)); // problem here
console.log(error);
});
};
function failure(error) { return { type:Constants.GETALL_FAILURE, error } }
}
if i add dispatch(alertActions.error(error)) in the catch the code is making repeated call to the api.
I am calling my action in componentWillMount like below
componentWillMount() {
const { dispatch } = this.props;
dispatch(ReviewActions.getByStatus(1));
}
when i remove dispatch(alertActions.error(error)), It is not making repeated call.
Can you please hlep me
How can i dispatch alert action in my code ?
What went wrong when i
add alert action dispatch?

Related

React and Redux toolkit - reject after promise

I'm working on a React Native app. I have a signup screen which has a button, onclick:
const handleClick = (country: string, number: string): void => {
dispatch(registerUser({ country, number }))
.then(function (response) {
console.log("here", response);
navigation.navigate(AuthRoutes.Confirm);
})
.catch(function (e) {
console.log('rejected', e);
});
};
The registerUser function:
export const registerUser = createAsyncThunk(
'user/register',
async ({ country, number }: loginDataType, { rejectWithValue }) => {
try {
const response = await bdzApi.post('/register', { country, number });
return response.data;
} catch (err) {
console.log(err);
return rejectWithValue(err.message);
}
},
);
I have one of my extraReducers that is indeed called, proving that it's effectively rejected.
.addCase(registerUser.rejected, (state, {meta,payload,error }) => {
state.loginState = 'denied';
console.log(`nope : ${JSON.stringify(payload)}`);
})
But the signup component gets processed normally, logging "here" and navigating to the Confirm screen. Why is that?
A thunk created with createAsyncThunk will always resolve but if you want to catch it in the function that dispatches the thunk you have to use unwrapResults.
The thunks generated by createAsyncThunk will always return a resolved promise with either the fulfilled action object or rejected action object inside, as appropriate.
The calling logic may wish to treat these actions as if they were the original promise contents. Redux Toolkit exports an unwrapResult function that can be used to extract the payload of a fulfilled action or to throw either the error or, if available, payload created by rejectWithValue from a rejected action:
import { unwrapResult } from '#reduxjs/toolkit'
// in the component
const onClick = () => {
dispatch(fetchUserById(userId))
.then(unwrapResult)
.then(originalPromiseResult => {})
.catch(rejectedValueOrSerializedError => {})
}

React and promise issues with fetch method

i'm new in react and i've got some issues with asynchronous fetch data :
i want to fetch github users
function fetchUser(username) {
return fetch(`https://api.github.com/users/${username}`)
.then(response => response.json())
.then(data => data)
}
export function getUserData(username) {
const object = {
profile: fetchUser(username),
}
console.log(object)
return object
}
and this is my method in my component
componentDidMount() {
getUserData(this.props.playerOne)
}
but this is what i got in my console
{profile: Promise}
i'm sure that i dont understand well this promise so could you help me to have not a Promise in my object but the data i'm fetching ? (if i log data in my fetch i got what i want)
You can make this function async and wait for the promise resolution.
export async function getUserData(username) {
const object = {
profile: await fetchUser(username),
}
console.log(object)
return object
}
then in your componentDidMount
componentDidMount() {
getUserData(this.props.playerOne)
.then((profile) => { this.setState({ profile }) })
}

redux doesn't update props immediately

I have a functional component in a react native application, and I'm dispatching an HTTP call. If some error occurs, I store it in redux, the problem is when I access to the error value, I get null
function IncomingOrder(props) {
function acceptOrder(orderId, coords) { // I call this from a button
const response = await actions
.accept(token, orderId, coords) //this is the async function
.catch((e) => {
showError(props.error);
});
}
...
}
...
function mapStateToProps(state) {
return {
token: state.auth.token,
error: state.deliveries.error
};
}
function mapDispatchToProps(dispatch) {
return {
actions: {
accept: (token, id, coords) => {
return dispatch(Order.accept(token, id, coords));
}
}
};
}
The problem is most likely on acceptOrder() async action creator.
Redux dispatch wont update immediately after your promise resolves/rejects. So by the time your error handler (Promise.prototype.catch or catch(){}) kicks in, there is no guarantee the action has been dispatched or the state tree updated.
What you want to do instead is to have this logic on the async action creator
// action module Order.js
export const accept = (token, id, coords) => dispatch => {
fetch('/my/api/endpoint')
.then(response => doSomethingWith(response))
.catch(err => dispatch(loadingFailed(err)); // <----- THIS is the important line
}
// reducer.js
// you want your reducer here to handle the action created by loadingFailed action creator. THEN you want to put the error in the state.
// IncomingOrder.js
function IncomingOrder(props) {
function acceptOrder(orderId, coords) { // I call this from a button
actions.accept(token, orderId, coords);
// You don't need to wait for this, as you're not gonna work with the result of this call. Instead, the result of this call is put on the state by your reducer.
}
render() {
const { error } => this.props; // you take the error from the state, which was put in here by loadingFailed()
if (error) {
// render your error UI
} else {
// render your regular UI
}
}
}
function mapStateToProps(state) {
return {
token: state.auth.token,
error: state.deliveries.error // <--- I suppose your error is already here (?)
};
}

react-axios-middleware promise handling

I am working on a React application which uses redux-axios-middleware to do XHR requests. The way it is setup is with a client in the index.js like so:
const client = axios.create({ //all axios can be used, shown in axios documentation
baseURL:'https://api.vktest.xxx/api',
responseType: 'json',
validateStatus: function (status) {
return status >= 200 && status < 300
}
});
const axiosMiddle = axiosMiddleware(client)
export const store = createStore(rootReducer, composeEnhancers(applyMiddleware(thunk, axiosMiddle)))
From my component I am then calling my action like so:
store.dispatch(loginAction(credentials))
This is the action:
import { LOGIN } from './types'
export function loginAction(credentials) {
return dispatch => {
return dispatch(doLogin(credentials) ).then(
response => {
dispatch({type: 'LOGIN_SUCCESS', response})
},
error => {
dispatch({type: 'LOGIN_FAIL', error})
throw error
}
)
}
}
function doLogin(credentials) {
return {
type: LOGIN,
payload: {
request: {
url: '/login/',
method: 'POST',
data: {
username: credentials.username,
password: credentials.password
}
}
}
}
}
It dispatches the doLogin function which is captured by axios-middleware to do the XHR call. After the call the axios promise contains either the type LOGIN_SUCCESS or LOGIN_FAIL. Axios automatically dispatches these types and the reducers can act on them. So far so good.
However (and I am trying to be as clear as possible), when the axios request fails, the axios promise is rejected and the LOGIN_FAIL type is dispatched. Since that promise is resolved the .then(..).catch(..) block is alway's calling the .then. I cannot seem to handle the axios error with doing something ugly in the .then block which handles the dispatch callback and not the axios callback.
To clearify the following is happening:
store.dispatch -> action -> doLogin -> axios -> reducer -> back to action dispatch.
I want to handle XHR errors between the axios and reducer step but when I do:
import { LOGIN } from './types'
export function loginAction(credentials) {
return dispatch => {
return dispatch(doLogin(credentials).then(
response => {
dispatch({type: 'LOGIN_SUCCESS', response})
},
error => {
dispatch({type: 'LOGIN_FAIL', error})
throw error
}
))
}
}
function doLogin(credentials) {
return {
type: LOGIN,
payload: {
request: {
url: '/login/',
method: 'POST',
data: {
username: credentials.username,
password: credentials.password
}
}
}
}
}
I get the following error:
Uncaught TypeError: doLogin(...).then is not a function
at authentication.js:6
The reason I want to fix this is because the current setup dispatches the following:
As seen in the image both the LOGIN_FAIL (axios) and the LOGIN_SUCCESS (dispatch) are being called. I want to be able to handle the axios error myself since I want to be able to for example give the user feedback of a failed login attempt. Somehow I am not able to figure out how.
Can someone help me?
I think I know what you mean.
You can change that behavior by setting returnRejectedPromiseOnError in middleware config.
Note that promise is returned only to chain actions. If you set this option you will have to always apply catch callback or you will be getting unhandled rejection when calling Promise errors. And this might be causing some issues when handled in components, because these callbacks may be fired when component is already unmounted.
Im my code I try to avoid then as much as possible to avoid these side effects but when I have to I do it like:
fetchSomething.then(action => {
if(action.type.endsWith('SUCCESS')) {
....
}
})

Dispatch async redux action from non-react component with thunk middleware

I am building an react / redux webapp where I am using a service to make all my API calls. Whenever the API returns 401 - Unauthorized I want to dispatch a logout action to my redux store.
The problem is now that my api-service is no react component, so I cannot get a reference to dispatch or actions.
What I did first was exporting the store and calling dispatch manually, but as I read here How to dispatch a Redux action with a timeout? that seems to be a bad practice because it requires the store to be a singleton, which makes testing hard and rendering on the server impossible because we need different stores for each user.
I am already using react-thunk (https://github.com/gaearon/redux-thunk) but I dont see how I can injectdispatch` into non-react components.
What do I need to do? Or is it generally a bad practice to dispatch actions outside from react components?
This is what my api.services.ts looks like right now:
... other imports
// !!!!!-> I want to get rid of this import
import {store} from '../';
export const fetchWithAuth = (url: string, method: TMethod = 'GET', data: any = null): Promise<TResponseData> => {
let promise = new Promise((resolve, reject) => {
const headers = {
"Content-Type": "application/json",
"Authorization": getFromStorage('auth_token')
};
const options = {
body: data ? JSON.stringify(data) : null,
method,
headers
};
fetch(url, options).then((response) => {
const statusAsString = response.status.toString();
if (statusAsString.substr(0, 1) !== '2') {
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
store.dispatch(UserActions.logout());
}
reject();
} else {
saveToStorage('auth_token', response.headers.get('X-TOKEN'));
resolve({
data: response.body,
headers: response.headers
});
}
})
});
return promise;
};
Thanks!
If you are using redux-thunk, you can return a function from an action creator, which has dispatch has argument:
const doSomeStuff = dispatch => {
fetch(…)
.then(res => res.json())
.then(json => dispatch({
type: 'dostuffsuccess',
payload: { json }
}))
.catch(err => dispatch({
type: 'dostufferr',
payload: { err }
}))
}
Another option is to use middleware for remote stuff. This works the way, that middle can test the type of an action and then transform it into on or multiple others. have a look here, it is similar, even if is basically about animations, the answer ends with some explanation about how to use middleware for remote requests.
maybe you can try to use middleware to catch the error and dispatch the logout action,
but in that case, the problem is you have to dispatch error in action creator which need to check the log status
api: throw the error
if (statusAsString === '401') {
// !!!!!-> here I need to dispatch the logout action
throw new Error('401')
}
action creator: catch error from api, and dispatch error action
fetchSometing(ur)
.then(...)
.catch(err => dispatch({
type: fetchSometingError,
err: err
})
middleware: catch the error with 401 message, and dispatch logout action
const authMiddleware = (store) => (next) => (action) => {
if (action.error.message === '401') {
store.dispatch(UserActions.logout())
}
}
You should have your api call be completely independent from redux. It should return a promise (like it currently does), resolve in the happy case and reject with a parameter that tells the status. Something like
if (statusAsString === '401') {
reject({ logout: true })
}
reject({ logout: false });
Then in your action creator code you would do:
function fetchWithAuthAction(url, method, data) {
return function (dispatch) {
return fetchWithAuth(url, method, data).then(
({ data, headers }) => dispatch(fetchedData(data, headers)),
({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
} else {
dispatch(fetchedDataFailed());
}
);
};
}
Edit:
If you don't want to write the error handling code everywhere, you could create a helper:
function logoutOnError(promise, dispatch) {
return promise.catch(({ logout }) => {
if(logout) {
dispatch(UserActions.logout());
}
})
}
Then you could just use it in your action creators:
function fetchUsers() {
return function (dispatch) {
return logoutOnError(fetchWithAuth("/users", "GET"), dispatch).then(...)
}
}
You can also use axios (interceptors) or apisauce (monitors) and intercept all calls before they goes to their handlers and at that point use the
// this conditional depends on how the interceptor works on each api.
// In apisauce you use response.status
if (response.status === '401') {
store.dispatch(UserActions.logout())
}

Resources