Error handling Apollo-client with redux-observable - reactjs

I'm doing a mutation using Apollo-client and redux-observable and so far this is my code:
export const languageTimeZoneEpic = (action$) => {
return action$.ofType('PING')
.flatMap(action => client.mutate({
mutation: languageTimeZoneIdMutation,
variables: { id: action.id, defaultLanguage: action.selected_language, defaultTimeZoneId: action.selected_timeZone }
})
.then(store.dispatch(setLocale(action.selected_language)))
)
.map(result => ({
type: 'PONG',
payload: result
}))
.catch(error => ({
type: 'PONG_ERROR'
}));
};
My mutation works correctly but I can't seem to make my catch(error) work. In the small amount of documentation I've found on this, it suggests I put Observable of after error => but then it gives me an error saying Observable is undefined.
Thank you
UPDATE:
If the connection between the app and the server doesn't work, it just waits for the connection to come back up and then finish the epic. I would like for it to just catch and error and stop the epic.

Figured out there was nothing wrong with the catch(error) in the code above. Turns out putting the server on hold did not throw an error when I tried to establish a connection. It had to be shut down for it to throw an error.
For those who are looking for a good way to implement a mutation using Apollo-client with redux-observable (because there is almost no docs on it), here's the code I used for my mutation:
import { languageMutation } from '../mutation/languageMutation';
import { changeLanguageFulfilled, changeLanguageError } from '../actions/actions';
export const languageEpic = (action$) => {
return action$.ofType('CHANGE_LANGUAGE')
.mergeMap(action => client.mutate({
mutation: languageMutation,
variables: { id: action.id, defaultLanguage: action.selected_language, defaultTimeZoneId: action.selected_timeZone }
}).then(result => changeLanguageFulfilled(result))
.catch(error => changeLanguageError(error))
);
};
Both changeLanguageFulfilled and changeLanguageError are actions that trigger their own reducer.
Hope this helps someone.

Related

Redux dispatch function not triggered within catch block upon testing using Jest and Enzyme

I'm currently working on testing a react and redux course project using Jest and Enzyme. I'm running into an issue when I test the redux action methods. I have a function called requestRobots that performs an async operation to fetch data. Upon fetching and depending on the promise result, the action within try/catch block is dispatched. This logic is handled using the redux-thunk middleware. The issue I'm facing is that when I test this method using Jest, the dispatch within the catch block is not triggered although the error is captured and logged. I also checked if the catch block is working in the actual project and it does dispatch the action in the project when there is an error. However this is not the case when I test it using Jest. I have used redux-mock-store for setting up the mock redux store and apiCall used in the code snippet is just an abstraction of the fetch API call
Could someone please help me out in fixing this issue.
I have attached snippets of my test and action code and screenshot of the logs that I get on running the test.
action.test.js
it("Test request robots action when called - unsuccessful state", async () => {
const apiCall = jest.fn().mockReturnValue(Promise.reject("Not found"));
const store = mockStore();
await store.dispatch(actions.requestRobots(apiCall));
const action = store.getActions();
console.log(action);
expect(apiCall).toBeCalled();
expect(apiCall.mock.calls.length).toBeGreaterThan(0);
console.log(apiCall.mock);
});
action.js
export const requestRobots = apiCall => dispatch => {
dispatch({ type: REQUEST_ROBOTS_PENDING });
apiCall("https://jsonplaceholder.typicode.com/users")
.then(data => dispatch({ type: REQUEST_ROBOTS_SUCCESS, payload: data }))
.catch(error => {
console.log(error);
dispatch({ type: REQUEST_ROBOTS_FAILED, payload: error });
});
};
Output logs output obtained after running the action.test.js file
Thanks in advance
return keyword is missing. use return keyword before api call like below
export const requestRobots = apiCall => dispatch => {
dispatch({ type: REQUEST_ROBOTS_PENDING });
return apiCall("https://jsonplaceholder.typicode.com/users")
.then(data => dispatch({ type: REQUEST_ROBOTS_SUCCESS, payload: data }))
.catch(error => {
console.log(error);
dispatch({ type: REQUEST_ROBOTS_FAILED, payload: error });
});
};
if this solution doesn’t work use return keyword before catch block's dispatch like below
export const requestRobots = apiCall => dispatch => {
dispatch({ type: REQUEST_ROBOTS_PENDING });
return apiCall("https://jsonplaceholder.typicode.com/users")
.then(data => dispatch({ type: REQUEST_ROBOTS_SUCCESS, payload: data }))
.catch(error => {
console.log(error);
return dispatch({ type: REQUEST_ROBOTS_FAILED, payload: error });
});
};

React Redux - Waiting for async api call to finish before next action is dispatched

I am working on a web app that uses React + Redux, with a backend using Django (DRF). I am using axios to send in my API request, which is asynchronous. The issue I am facing right now is that the actions dispatched do not wait for the API call to finish before the next action is dispatched. Below is my code
const mapDispatchToProps = dispatch => ({
success: id => {
dispatch(fetchSalesProject(id));
dispatch(createMessage('Requirement successfully updated!'))
}
})
fetchSalesProject action (axiosInstance is just a custom modification of axios call, the functionality is the same)
export const fetchSalesProject = (id) => (dispatch) => {
console.log('enter sales project action')
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data')
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
createMessage action
export const createMessage = (message) => {
console.log('message')
return {
type: CREATE_MESSAGE,
message: message,
};
};
When calling this.props.success (refer to mapDispatchToProps), the message is displayed before the api call response data is received (evident by the fact that console.log('message') runs before console.log('fetched data'))
I would want the data to be fetched from the api call before i run the createMessage action, is there any advise on how to accomplish that? I am new to React and especially Redux, so hope that you guys can point me in the right direction on how to accomplish that.
Also, can I check whether it is wrong to have a dispatch in the mapDispatchToProps, and also a dispatch within the action (refer to fetchSalesProject action). Would it cause any issues with performance or is it frowned upon to do so? Please advise me as I am quite confused with Redux.
Thanks all for reading through, all help is appreciated :-)
while you are dispatching from UI, you just sending an object towards reducer which in his turn will modify the state at the store and in the end of the process will re-render components that refer to props that changed. At the moment you are dispatching the first action, there is nothing that tells the component that it should wait before sending the next object to the reducer
So you have 2 options,
the first is at UI itself use componentDidUpdate or useEffect for run the second action after the first action reduced
componentDidUpdate(prevProps) {
if (prevProps.salesProject != this.props.salesProject)
dispatch(createMessage('Requirement successfully updated!'))
}
while I assume that dispatch(fetchSalesProject(id)); modify salesProject prop
Another way to do that, and in case you actually fine with that message and salesProject will gonna get together to the reducer, is to dispatch them in one action
export const fetchSalesProjectAndMessage = (id, message) => (dispatch) => {
console.log('enter sales project action')
axiosInstance
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data')
dispatch({
type: FETCH_SALES_PROJECT_AND_MESSAGE,
payload: { data: res.data, message }
});
})
.catch((err) => dispatch(returnErrors(err.response.data, err.response.status)));
};
and at reducer payload.data either payload.message will refer to desired info
There is a better way of doing this that does not force you combine two action creators. When your thunk action creator returns a promise then you can wait for it. Your current code did not return the axios promise but if you do return it you can do the following:\
const mapDispatchToProps = (dispatch) => ({
success: (id) => {
dispatch(fetchSalesProject(id)).then(() =>
dispatch(//wait for fetchSalesProject to finish
createMessage('Requirement successfully updated!')
)
);
},
});
export const fetchSalesProject = (id) => (dispatch) => {
console.log('enter sales project action');
return axiosInstance //you did not return anything here
.get(`/sales-project/detail/${id}/`)
.then((res) => {
console.log('fetched data');
dispatch({
type: FETCH_SALES_PROJECT,
payload: res.data,
});
})
.catch((err) => {
dispatch(
returnErrors(err.response.data, err.response.status)
);
//return rejected promise here
return Promise.reject(err);
});
};

Why graphQLErrors are always empty in react components?

I want to show some errors that comes from graphql server to user.
Have some component with callback that use some mutation
onSave() => {
this.props.mutate({
mutation: CHANGE_ORDER_MUTATION,
variables: {
ids,
},
}).catch((e) => {
console.log(e.graphQLErrors) <--- e.graphQLErrors is always empty array = []
})
}
While I'm able to see the graphQLErrors error with apollo-link-error link.
const errorLink = onError(({ graphQLErrors, networkError }) => {
console.log(graphQLErrors) <--- errors from server
});
Migration to apollo-server instead of express-graphql solve the problem.
Or you can access errors by e.networkError.result.errors
As I understand it, the 'catch' will catch errors if your server returns an error code, but not if it returns your errors in the response, but with a success code. In that case, you just need to get your errors out of the response (keeping your catch too in case the server responds with an error code):
this.props.mutate({
mutation: CHANGE_ORDER_MUTATION,
variables: {
ids,
},
})
.then((response) => {
if (response.errors) {
// do something with the errors
} else {
// there wasn't an error
}
})
.catch((e) => {
console.log(e.graphQLErrors) <--- e.graphQLErrors is always empty array = []
})
Alternatively - if you use the graphql() option from react-apollo, you can specify an onError handler:
export default graphql(CHANGE_ORDER_MUTATION, {
options: (props) => ({
onCompleted: props.onCompleted,
onError: props.onError
}),
})(MyComponent);
references:
https://github.com/apollographql/react-apollo/issues/1828
Apollo client mutation error handling

What is the correct way of handling firebase user creation flow in react-redux application

I've been writing my React/Rediux/Firebase app for some time (started ~1year ago, but I had some few months breaks during this time - so I can review my own code from a time perspective).
Now I'm checking the code again and I have gut feeling that it's not the state of the art.
I am using Firebase also for managing account, in this case for creating a new one
I put all the chain of actions related to creation of user and error handling in one function block in Actions/index.js.
export const signUpUser = (data) => dispatch => {
Firebase.auth().createUserAndRetrieveDataWithEmailAndPassword(data.email, data.password)
.then(response => {
const userId = Firebase.auth().currentUser()
getUserInfoRef(userId).set({
uid: userId,
isAdmin: false
})
})
.then(response => {
Firebase.auth().currentUser().updateProfile({
displayName: `${data.name} ${data.surname}`
})
})
.then(() => {
dispatch(sendEmailVerification())
})
.catch(error => {
console.log('Error during signUpUser', error)
dispatch(authError(error))
})
}
but is this a good approach?
Isn't dispatching actions from its body some kind of anti pattern?
Maybe it should be split somehow (how?) ?
It's working, but I'm not delighted by how it looks :)
Please, advise.
I don't see anything wrong with your code. As long as you are using promise with the then, catch blocks it has to dispatched within.
But if you still want it to look better i will suggest you use async/await like this:
export const signUpUser = (data) => async dispatch => {
try {
await Firebase.auth().createUserAndRetrieveDataWithEmailAndPassword(data.email, data.password)
const userId = await Firebase.auth().currentUser()
getUserInfoRef(userId).set({
uid: userId,
isAdmin: false
}) // am not sure if this func is async or not
await Firebase.auth().currentUser().updateProfile({
displayName: `${data.name} ${data.surname}`
})
dispatch(sendEmailVerification())
} catch(e) {
console.log('Error during signUpUser', error)
dispatch(authError(error))
}
}
PS: It's nice to see SO asking people to be "nice" here. I think it's very much needed.

recompose withHandlers ... asynchronously?

Is is possible/safe to use withHandlers with promises?
Ex.:
withHandlers({
onChange: props => event => {
props.callAPI(props.data)
.then(data => props.updateData(data))
},
...
Thanks!
After some tests I realized that it's working pretty well. Recompose rocks for building with pure components.
This is perfectly valid and working pretty well.
const enhWithHandlers = withHandlers({
loginUserMutation: props => args => {
props.updateMutationState(loading: true, error: null });
props.loginUser(args)
.then(() =>
props.updateMutationState({loading: false, error: null }))
.catch(err =>
props.updateMutationState({ loading: false, error: err }));
}
},
...
// then compose like
export default compose(
reduxConnect,
gqlConnectLogin,
gqlConnectRegister,
enhWithState,
enhWithHandlers
)(UserLoginRegister);
It helps me to overcome lack of ability to reflect results of graphQl mutation with Apollo client to the wrapped component. This handles it perfectly and without the need of side effects in the component itself.
But there are some problems when we use it like this:
compose(
withState('loginStatus', 'setLoginStatus', {loading: false, error:null}),
withHandlers({
loginUserMutation: props => async args => {
try {
props.setLoginStatus({loading: true, error: null});
await props.loginUser(args);
} catch(error) {
props.setLoginStatus({...props.loginStatus, error});
} finally {
props.setLoginStatus({...props.loginStatus, loading: false});
}
}
})
)
Because the props reference is indeed lost after we await props.loginUser(args). Then we use it after that is wrong.
We should notice not to use it like above.

Resources