I use openweathermap API to get forecast. App is based on ReactJS and Redux. I have a problem with catch errors. I want to create alert for users when searched city doesn't exists in database.
So, I have action like below:
export function fetchWeather (city) {
const url = `${ROOT_URL}&q=${city}`;
const request = axios.get(url);
return (dispatch) => {
request
.then(({data}) => {
dispatch({type: FETCH_WEATHER, payload: data})
})
.catch((error) => {
dispatch({type: FETCH_WEATHER_ERROR, payload: error})
});
};
And my reducer:
import { FETCH_WEATHER, FETCH_WEATHER_ERROR } from '../actions/index';
export default function (state = [], action) {
switch (action.type) {
case FETCH_WEATHER:
console.log(action.payload) //I receive object, so it's ok
return [...state, action.payload];
case FETCH_WEATHER_ERROR:
console.log(action.payload) // I receive just info in console "Error: Request failed with status code 404"
return state;
}
return state;
}
So, it works properly but I'm curious how to get proper object in error part to simple show alert with message info what happened wrong. Because when I check in inspector tab (Networks) there is nice object in response:
{cod: "404", message: "city not found"}, but in console.log(action.payload) I have just info, no object, array... Why are these things different? How to get proper value of error response to show error message?
It looks like the API will always return 200 (success) when the connection works even though there is a 401 not allowed or 404 not found. Check out the following url in your dev tools network tab:
http://samples.openweathermap.org/data/2.5/weather?q=nomatterNoApiKey
So anything going into catch is actual network problem.
request
.then((response) => {
if(response.cod===200){
dispatch({type: FETCH_WEATHER, payload: response.data});
}else{
dispatch({type: FETCH_WEATHER_ERROR, payload: response.message});
}
})
You have to make sure that is the correct way to use the API and still have to deal with network errors.
Related
I am building an application that works with my own API. (running nodejs server for it) and no matter what, the action creator will pass success actionCreator to reducer.
Here is my fetch action creator:
export function fetchData() {
return dispatch => {
dispatch(fetchBegin());
return fetch('/api/selectAll')
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchSuccess(json));
return json;
})
.catch(error => dispatch(fetchFailure(error)));
};
}
Here are the other functions in "index.js" actions
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const fetchBegin = () => ({
type: FETCH_DATA_BEGIN
});
export const fetchSuccess = data => ({
type: FETCH_DATA_SUCCESS,
payload: data
});
export const fetchFailure = error => ({
type: FETCH_DATA_FAILURE,
payload: error
});
Here is my travelReducer, I have imported the action types at the top of the file
const initialState = {
items: [],
loading: false,
error: null
};
export default function travelReducer(state = initialState, action) {
console.log('action :', action.type);
switch (action.type) {
case FETCH_DATA_BEGIN:
// Mark the state as "loading" so we can show a spinner or something
// Also, reset any errors. We're starting fresh.
return {
...state,
loading: true,
error: null
};
case FETCH_DATA_SUCCESS:
// All done: set loading "false".
// Also, replace the items with the ones from the server
return {
...state,
loading: false,
items: action.payload
};
case FETCH_DATA_FAILURE:
console.log('failure should be hit');
// The request failed, but it did stop, so set loading to "false".
// Save the error, and we can display it somewhere
// Since it failed, we don't have items to display anymore, so set it empty.
return {
...state,
loading: false,
error: action.payload,
items: []
};
default:
// ALWAYS have a default case in a reducer
return state;
}
}
I then make use of this in the travelList container. I also import the fetchTravel() function and call this.props.fetchTravel() on componentDidMount()
const mapStateToProps = state => ({
data: state.travel.items,
loading: state.travel.loading,
error: state.travel.error
});
// // anything returned from this func will end up as props
// // on this container (this.props.fetchTravelData)
function mapDispatchToProps(dispatch) {
// whenever fetchTravelData is called, the result should be passed to
// ALL reducers
return bindActionCreators({ fetchData: fetchData }, dispatch);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TravelList);
My main issue is not getting it to fetch as intended, it does that, but the error actionCreator never gets called, even though I can console.log "error caught" in the .catch() and it will log. But no update in the reducer.
To check this I would console.log the action.type and no matter what, even with "error caught" logging, the action type would be FETCH_DATA_SUCCESS.
Pulling my hair out, so any advice would be great.
thanks.
I don't believe your error is in the code you provided. If you are successfully logging to the console in the catch that dispatches fetchFailure, then you have proven that your fetch logic is working. The next debug step is likely to console.log(error) to make sure you are receiving what you believe you are receiving as well as console.log(fetchFailure(error)) to make sure the action you are dispatching is what you believe you are dispatching.
If fetchFailure(error) is returning the error action, there is no reason for the reducer to be receiving the success action: at least not in the code that you provided.
OK I AM AN IDIOT :) I just checked the actionTypes I have and I realised I had fetch data success down twice, for both failure and success. I appreciate you taking the time to give advice. Marking as answered
Since you're throwing an error, you need to reference the error message in your reducer instead of the error object, which is what currently gets passed to your catch handler.
case FETCH_DATA_FAILURE:
console.log('failure should be hit');
// The request failed, but it did stop, so set loading to "false".
// Save the error, and we can display it somewhere
// Since it failed, we don't have items to display anymore, so set it empty.
return {
...state,
loading: false,
error: action.payload.message,
items: []
I have a search of weather for some cities. I would like to create info modal when a user tries to find a city that is not in the base. In this case I receive 404 error from my API.
I fetch the data every time when user click on search button. I use axios to do it and whole project is based on React and Redux. Everything is clear for me but I have a problem with pass valid response to payload.
How should I do it? In an another file and use react component lifecycle?
action.js
export function fetchWeather(city) {
const url = `${ROOT_URL}&q=${city}`;
axios.get(url)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
return {
type: FETCH_WEATHER,
payload: request
};
}
In your example the return will be called before Axios completes it's API call, because it's asynchronous. One solution to this is to put the return inside the .then like this:
export function fetchWeather(city) {
const url = `${ROOT_URL}&q=${city}`;
axios.get(url)
.then(function (response) {
// won't get called until the API call completes
console.log(response);
return {
type: FETCH_WEATHER,
payload: response.data
};
})
.catch(function (error) {
// won't get called until the API call fails
console.log(error);
return {
type: FETCH_WEATHER_ERROR,
payload: error
};
});
}
You should also return an error in the catch if the API call is unsuccessful.
In your snippet, request will always be undefined because axios.get is an async operation and return happens before axios.get finishes execution. You do something like this:
export async function fetchWeather(city) {
try {
const request = await axios.get(`${ROOT_URL}&q=${city}`);
// server returned a 2XX response (success)
return {
type: FETCH_WEATHER,
payload: request
};
} catch(error) {
// Network failure or 4XX or 5XX response.
return {
type: FETCH_WEATHER_FAIL
payload: error
}
}
}
I am running into a really frustrating issue. Where a simple json constant on the express server is sent as a json object, but when receiving this object, and trying to extract the errors from it on the client, the json object from the server comes through as undefined and for the life of me, I can't figure out why.
It seems that changing res.status(400).json(errors); to res.json(errors); from the server and extracting the error data from the client code block where isValid is true, I am able to get the error messages - therefore, sending the 400 status, may have something to do with it.
Has anyone else run in to this issue? i appreciate any suggestions on how to resolve.
Express - api.js
if( isValid ) {
res.json({success: true});
} else {
const errors = { username: 'This field is required',
email: 'Email is invalid' };
res.status(400).json(errors);
}
SignupForm Component
this.setState({errors: {}, isLoading: true});
this.props.userSignupRequest(this.state).then(
() => {
this.props.history.push('/');
},
({data}) => {
console.log(data); //undefined
this.setState({errors: data, isLoading: false})
}
)
SignupAction.js
import axios from 'axios';
export function userSignupRequest(userData) {
return dispatch => {
return axios.post('http://myhost/api/signup', userData);
}
}
As per the Axios manual:
When using catch, or passing a rejection callback as second parameter of then, the response will be available through the error object as explained in the Handling Errors section.
So:
this.props.userSignupRequest(this.state).then(
() => {
this.props.history.push('/');
},
error => {
const {data} = error.response;
console.log(data);
this.setState({errors: data, isLoading: false});
}
)
Imagine a situation of handling form submit which can return different errors: 400, 401, 500.
When 400 is returned, I want to show a message in top of the form (override the default behavior).
For other (unhandled) error codes, the default (global) error handler should be invoked (which shows notification toast). Just don't want to duplicate this code for every single action
I dispatch async actions using redux-thunk middleware
// Pseudo code
const action = (dispatch) => {
const onSuccess = (result) => dispatch({type: 'OPERATION_SUCCESS', payload: result});
const onError = (error) => dispatch({type: 'OPERATION_ERROR', error: true, payload: error});
return promise.then(onSuccess, onError);
};
dispatch(action);
I can create a reducer which handles all {error: true} actions and show some popup-notification (probably without using redux state, directly invoking some toast.show() method)
But how to determine if this special error was already handled by some other reducer?
By the time an action reaches a reducer, it is a fact. It reflects something that has already happened. There is no sense in asking “has other reducer handled this action?” because reducers are supposed to be passive and, in general sense, unaware of each other’s existence. They should strive to be independent, where possible.
There is no one “true” way to accomplish what you wanted, but since you already use the convention of treating any object with an error property as a global error, you might as well introduce another convention like “if the action has a suppressGlobalErrorNotification flag then the global error reducer should not care about it”.
// utilities
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response
} else {
const error = new Error(response.statusText)
error.response = response
throw error
}
}
function parseJSON(response) {
return response.json()
}
export function post(url, data) {
const options = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}
return fetch(url, options)
.then(checkStatus)
.then(parseJSON)
}
// action creators
import { post } from './utils'
export function submitForm(data) {
return dispatch => post('/myform', data).then(
response => dispatch({
type: 'SUBMIT_FORM_SUCCESS',
payload: response
}),
error => dispatch({
type: 'SUBMIT_FORM_FAILURE',
error: error,
suppressGlobalErrorNotification: (
error.response &&
error.response.status === 400
)
})
)
}
// reducers
export function error(state = null, action) {
if (!action.error || action.suppressGlobalErrorNotification) {
return state
}
if (action.type === 'RESET_ERROR') {
return null
}
return action.error
}
export function form(state = {}, action) {
switch (action.type) {
case 'SUBMIT_FORM_FAILURE':
return Object.assign({}, state, { isFormError: true })
// ...
default:
return state
}
}
Same things for me, I didn't find any working solution than can handle it.
#Dan Abramov showed the example but the issue here is when you have tens of forms things becomes more complicated. Each time you need to handle same things, duplicate code starts to be annoying.
For example:
form => client validation => CLIENT_VALIDATION_ERROR => reducer
FETCH_STARTED
form => form submit => SERVER_SIDE_SUCCESS => reducers
SERVER_SIDE_ERROR
There can be an exception where we need to handle such behaviour manually but in most cases it's not.
I have one reducer for Clients, one other for AppToolbar and some others...
Now lets say that I created a fetch action to delete client, and if it fails I have code in the Clients reducer which should do some stuff, but also I want to display some global error in AppToolbar.
But the Clients and the AppToolbar reducers do not share the same part of the state and I cannot create a new action in the reducer.
So how am I suppose to show global error? Thanks
UPDATE 1:
I forget to mention that I use este devstack
UPDATE 2:
I marked Eric's answer as correct, but I have to say that solution which I am using in este is more like combination of Eric and Dan's answer...
You just have to find what fits you the best in your code...
If you want to have the concept of "global errors", you can create an errors reducer, which can listen for addError, removeError, etc... actions. Then, you can hook into your Redux state tree at state.errors and display them wherever appropriate.
There are a number of ways you could approach this, but the general idea is that global errors/messages would merit their own reducer to live completely separate from <Clients />/<AppToolbar />. Of course if either of these components needs access to errors you could pass errors down to them as a prop wherever needed.
Update: Code Example
Here is one example of what it might look like if you were to pass the "global errors" errors into your top level <App /> and conditionally render it (if there are errors present). Using react-redux's connect to hook up your <App /> component to some data.
// App.js
// Display "global errors" when they are present
function App({errors}) {
return (
<div>
{errors &&
<UserErrors errors={errors} />
}
<AppToolbar />
<Clients />
</div>
)
}
// Hook up App to be a container (react-redux)
export default connect(
state => ({
errors: state.errors,
})
)(App);
And as far as the action creator is concerned, it would dispatch (redux-thunk) success failure according to the response
export function fetchSomeResources() {
return dispatch => {
// Async action is starting...
dispatch({type: FETCH_RESOURCES});
someHttpClient.get('/resources')
// Async action succeeded...
.then(res => {
dispatch({type: FETCH_RESOURCES_SUCCESS, data: res.body});
})
// Async action failed...
.catch(err => {
// Dispatch specific "some resources failed" if needed...
dispatch({type: FETCH_RESOURCES_FAIL});
// Dispatch the generic "global errors" action
// This is what makes its way into state.errors
dispatch({type: ADD_ERROR, error: err});
});
};
}
While your reducer could simply manage an array of errors, adding/removing entries appropriately.
function errors(state = [], action) {
switch (action.type) {
case ADD_ERROR:
return state.concat([action.error]);
case REMOVE_ERROR:
return state.filter((error, i) => i !== action.index);
default:
return state;
}
}
Erik’s answer is correct but I would like to add that you don’t have to fire separate actions for adding errors. An alternative approach is to have a reducer that handles any action with an error field. This is a matter of personal choice and convention.
For example, from Redux real-world example that has error handling:
// Updates error message to notify about the failed fetches.
function errorMessage(state = null, action) {
const { type, error } = action
if (type === ActionTypes.RESET_ERROR_MESSAGE) {
return null
} else if (error) {
return error
}
return state
}
The approach I'm currently taking for a few specific errors (user input validation) is to have my sub-reducers throw an exception, catch it in my root reducer, and attach it to the action object. Then I have a redux-saga that inspects action objects for an error and update the state tree with error data in that case.
So:
function rootReducer(state, action) {
try {
// sub-reducer(s)
state = someOtherReducer(state,action);
} catch (e) {
action.error = e;
}
return state;
}
// and then in the saga, registered to take every action:
function *errorHandler(action) {
if (action.error) {
yield put(errorActionCreator(error));
}
}
And then adding the error to the state tree is as Erik describes.
I use it pretty sparingly, but it keeps me from having to duplicate logic which legitimately belongs in the reducer (so it can protect itself from an invalid state).
write custom Middleware to handle all the api related error. In this case your code will be more cleaner.
failure/ error actin type ACTION_ERROR
export default (state) => (next) => (action) => {
if(ACTION_ERROR.contains('_ERROR')){
// fire error action
store.dispatch(serviceError());
}
}
what I do is I centralize all error handling in the effect on a per effect basis
/**
* central error handling
*/
#Effect({dispatch: false})
httpErrors$: Observable<any> = this.actions$
.ofType(
EHitCountsActions.HitCountsError
).map(payload => payload)
.switchMap(error => {
return of(confirm(`There was an error accessing the server: ${error}`));
});
You can use axios HTTP client. It already has implemented Interceptors feature. You can intercept requests or responses before they are handled by then or catch.
https://github.com/mzabriskie/axios#interceptors
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});