Improve request error handling

This commit is contained in:
Arnaud Vergnet 2021-05-13 19:10:28 +02:00
parent a1cfb0385a
commit d55c692bd3
8 changed files with 100 additions and 78 deletions

View file

@ -1,10 +1,17 @@
import React, { useEffect, useRef } from 'react';
import ErrorView from './ErrorView';
import { useRequestLogic } from '../../utils/customHooks';
import { useFocusEffect } from '@react-navigation/native';
import {
useFocusEffect,
useNavigation,
useRoute,
} from '@react-navigation/native';
import BasicLoadingScreen from './BasicLoadingScreen';
import i18n from 'i18n-js';
import { API_REQUEST_CODES, REQUEST_STATUS } from '../../utils/Requests';
import { StackNavigationProp } from '@react-navigation/stack';
import { MainRoutes } from '../../navigation/MainNavigator';
import ConnectionManager from '../../managers/ConnectionManager';
export type RequestScreenProps<T> = {
request: () => Promise<T>;
@ -37,6 +44,8 @@ type Props<T> = RequestScreenProps<T>;
const MIN_REFRESH_TIME = 5 * 1000;
export default function RequestScreen<T>(props: Props<T>) {
const navigation = useNavigation<StackNavigationProp<any>>();
const route = useRoute();
const refreshInterval = useRef<number>();
const [
loading,
@ -89,22 +98,42 @@ export default function RequestScreen<T>(props: Props<T>) {
}, [props.cache, props.refreshOnFocus])
);
const isErrorCritical = (e: API_REQUEST_CODES | undefined) => {
return e === API_REQUEST_CODES.BAD_TOKEN;
};
useEffect(() => {
if (isErrorCritical(code)) {
ConnectionManager.getInstance()
.disconnect()
.then(() => {
navigation.replace(MainRoutes.Login, { nextScreen: route.name });
});
}
}, [code, navigation, route]);
if (data === undefined && loading && props.showLoading !== false) {
return <BasicLoadingScreen />;
} else if (
data === undefined &&
status !== REQUEST_STATUS.SUCCESS &&
(status !== REQUEST_STATUS.SUCCESS ||
(status === REQUEST_STATUS.SUCCESS && code !== undefined)) &&
props.showError !== false
) {
return (
<ErrorView
status={status}
code={code}
loading={loading}
button={{
icon: 'refresh',
text: i18n.t('general.retry'),
onPress: () => refreshData(),
}}
button={
isErrorCritical(code)
? undefined
: {
icon: 'refresh',
text: i18n.t('general.retry'),
onPress: () => refreshData(),
}
}
/>
);
} else {

View file

@ -156,7 +156,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
this.categories = data?.categories;
return [{ title: '', data: data.clubs }];
} else {
return [{ title: '', data: [] }];
return [];
}
};

View file

@ -136,7 +136,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
}
return [{ title: '', data: data.devices }];
} else {
return [{ title: '', data: [] }];
return [];
}
};

View file

@ -110,10 +110,9 @@ class LoginScreen extends React.Component<Props, StateType> {
this.onInputChange(false, value);
};
props.navigation.addListener('focus', this.onScreenFocus);
// TODO remove
this.state = {
email: 'vergnet@etud.insa-toulouse.fr',
password: 'IGtt25ùj',
email: '',
password: '',
isEmailValidated: false,
isPasswordValidated: false,
loading: false,
@ -373,7 +372,7 @@ class LoginScreen extends React.Component<Props, StateType> {
* Saves the screen to navigate to after a successful login if one was provided in navigation parameters
*/
handleNavigationParams() {
this.nextScreen = this.props.route.params.nextScreen;
this.nextScreen = this.props.route.params?.nextScreen;
}
/**

View file

@ -121,12 +121,16 @@ function GroupSelectionScreen() {
}
| undefined
): Array<{ title: string; data: Array<PlanexGroupCategoryType> }> => {
return [
{
title: '',
data: generateData(fetchedData),
},
];
if (fetchedData) {
return [
{
title: '',
data: generateData(fetchedData),
},
];
} else {
return [];
}
};
/**
@ -181,39 +185,34 @@ function GroupSelectionScreen() {
* @returns {[]}
*/
const generateData = (
fetchedData: PlanexGroupsType | undefined
fetchedData: PlanexGroupsType
): Array<PlanexGroupCategoryType> => {
const data: Array<PlanexGroupCategoryType> = [];
if (fetchedData) {
// Convert the object into an array
Object.values(fetchedData).forEach(
(category: PlanexGroupCategoryType) => {
const content: Array<PlanexGroupType> = [];
// Filter groups matching the search query
category.content.forEach((g: PlanexGroupType) => {
if (stringMatchQuery(g.name, currentSearchString)) {
content.push(g);
}
});
// Only add categories with groups matching the query
if (content.length > 0) {
data.push({
id: category.id,
name: category.name,
content: content,
});
}
// Convert the object into an array
Object.values(fetchedData).forEach((category: PlanexGroupCategoryType) => {
const content: Array<PlanexGroupType> = [];
// Filter groups matching the search query
category.content.forEach((g: PlanexGroupType) => {
if (stringMatchQuery(g.name, currentSearchString)) {
content.push(g);
}
);
data.sort(sortName);
// Add the favorites at the top
data.unshift({
name: i18n.t('screens.planex.favorites.title'),
id: 0,
content: favoriteGroups,
});
}
// Only add categories with groups matching the query
if (content.length > 0) {
data.push({
id: category.id,
name: category.name,
content: content,
});
}
});
data.sort(sortName);
// Add the favorites at the top
data.unshift({
name: i18n.t('screens.planex.favorites.title'),
id: 0,
content: favoriteGroups,
});
return data;
};

View file

@ -349,13 +349,7 @@ function ProximoListScreen(props: Props) {
},
];
} else {
return [
{
title: '',
data: [],
keyExtractor: keyExtractor,
},
];
return [];
}
};

View file

@ -239,13 +239,7 @@ function ProximoMainScreen() {
},
];
} else {
return [
{
title: '',
data: [],
keyExtractor: getKeyExtractor,
},
];
return [];
}
};

View file

@ -27,6 +27,7 @@ import WebSectionList from '../../components/Screens/WebSectionList';
import type { SectionListDataType } from '../../components/Screens/WebSectionList';
import Urls from '../../constants/Urls';
import { readData } from '../../utils/WebData';
import { REQUEST_STATUS } from '../../utils/Requests';
type PropsType = {
navigation: StackNavigationProp<any>;
@ -109,25 +110,31 @@ class SelfMenuScreen extends React.Component<PropsType> {
* @return {[]}
*/
createDataset = (
fetchedData: Array<RawRuMenuType> | undefined
fetchedData: Array<RawRuMenuType> | undefined,
_loading: boolean,
_lastRefreshDate: Date | undefined,
_refreshData: (newRequest?: () => Promise<Array<RawRuMenuType>>) => void,
status: REQUEST_STATUS
): SectionListDataType<RuFoodCategoryType> => {
let result: SectionListDataType<RuFoodCategoryType> = [];
if (fetchedData == null || fetchedData.length === 0) {
result = [
{
title: i18n.t('general.notAvailable'),
data: [],
keyExtractor: this.getKeyExtractor,
},
];
} else {
fetchedData.forEach((item: RawRuMenuType) => {
result.push({
title: DateManager.getInstance().getTranslatedDate(item.date),
data: item.meal[0].foodcategory,
keyExtractor: this.getKeyExtractor,
if (status === REQUEST_STATUS.SUCCESS) {
if (fetchedData == null || fetchedData.length === 0) {
result = [
{
title: i18n.t('general.notAvailable'),
data: [],
keyExtractor: this.getKeyExtractor,
},
];
} else {
fetchedData.forEach((item: RawRuMenuType) => {
result.push({
title: DateManager.getInstance().getTranslatedDate(item.date),
data: item.meal[0].foodcategory,
keyExtractor: this.getKeyExtractor,
});
});
});
}
}
return result;
};