Improve request error handling
This commit is contained in:
parent
a1cfb0385a
commit
d55c692bd3
8 changed files with 100 additions and 78 deletions
|
@ -1,10 +1,17 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef } from 'react';
|
||||||
import ErrorView from './ErrorView';
|
import ErrorView from './ErrorView';
|
||||||
import { useRequestLogic } from '../../utils/customHooks';
|
import { useRequestLogic } from '../../utils/customHooks';
|
||||||
import { useFocusEffect } from '@react-navigation/native';
|
import {
|
||||||
|
useFocusEffect,
|
||||||
|
useNavigation,
|
||||||
|
useRoute,
|
||||||
|
} from '@react-navigation/native';
|
||||||
import BasicLoadingScreen from './BasicLoadingScreen';
|
import BasicLoadingScreen from './BasicLoadingScreen';
|
||||||
import i18n from 'i18n-js';
|
import i18n from 'i18n-js';
|
||||||
import { API_REQUEST_CODES, REQUEST_STATUS } from '../../utils/Requests';
|
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> = {
|
export type RequestScreenProps<T> = {
|
||||||
request: () => Promise<T>;
|
request: () => Promise<T>;
|
||||||
|
@ -37,6 +44,8 @@ type Props<T> = RequestScreenProps<T>;
|
||||||
const MIN_REFRESH_TIME = 5 * 1000;
|
const MIN_REFRESH_TIME = 5 * 1000;
|
||||||
|
|
||||||
export default function RequestScreen<T>(props: Props<T>) {
|
export default function RequestScreen<T>(props: Props<T>) {
|
||||||
|
const navigation = useNavigation<StackNavigationProp<any>>();
|
||||||
|
const route = useRoute();
|
||||||
const refreshInterval = useRef<number>();
|
const refreshInterval = useRef<number>();
|
||||||
const [
|
const [
|
||||||
loading,
|
loading,
|
||||||
|
@ -89,22 +98,42 @@ export default function RequestScreen<T>(props: Props<T>) {
|
||||||
}, [props.cache, props.refreshOnFocus])
|
}, [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) {
|
if (data === undefined && loading && props.showLoading !== false) {
|
||||||
return <BasicLoadingScreen />;
|
return <BasicLoadingScreen />;
|
||||||
} else if (
|
} else if (
|
||||||
data === undefined &&
|
data === undefined &&
|
||||||
status !== REQUEST_STATUS.SUCCESS &&
|
(status !== REQUEST_STATUS.SUCCESS ||
|
||||||
|
(status === REQUEST_STATUS.SUCCESS && code !== undefined)) &&
|
||||||
props.showError !== false
|
props.showError !== false
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<ErrorView
|
<ErrorView
|
||||||
status={status}
|
status={status}
|
||||||
|
code={code}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
button={{
|
button={
|
||||||
icon: 'refresh',
|
isErrorCritical(code)
|
||||||
text: i18n.t('general.retry'),
|
? undefined
|
||||||
onPress: () => refreshData(),
|
: {
|
||||||
}}
|
icon: 'refresh',
|
||||||
|
text: i18n.t('general.retry'),
|
||||||
|
onPress: () => refreshData(),
|
||||||
|
}
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -156,7 +156,7 @@ class ClubListScreen extends React.Component<PropsType, StateType> {
|
||||||
this.categories = data?.categories;
|
this.categories = data?.categories;
|
||||||
return [{ title: '', data: data.clubs }];
|
return [{ title: '', data: data.clubs }];
|
||||||
} else {
|
} else {
|
||||||
return [{ title: '', data: [] }];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ class EquipmentListScreen extends React.Component<PropsType, StateType> {
|
||||||
}
|
}
|
||||||
return [{ title: '', data: data.devices }];
|
return [{ title: '', data: data.devices }];
|
||||||
} else {
|
} else {
|
||||||
return [{ title: '', data: [] }];
|
return [];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -110,10 +110,9 @@ class LoginScreen extends React.Component<Props, StateType> {
|
||||||
this.onInputChange(false, value);
|
this.onInputChange(false, value);
|
||||||
};
|
};
|
||||||
props.navigation.addListener('focus', this.onScreenFocus);
|
props.navigation.addListener('focus', this.onScreenFocus);
|
||||||
// TODO remove
|
|
||||||
this.state = {
|
this.state = {
|
||||||
email: 'vergnet@etud.insa-toulouse.fr',
|
email: '',
|
||||||
password: 'IGtt25ùj',
|
password: '',
|
||||||
isEmailValidated: false,
|
isEmailValidated: false,
|
||||||
isPasswordValidated: false,
|
isPasswordValidated: false,
|
||||||
loading: 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
|
* Saves the screen to navigate to after a successful login if one was provided in navigation parameters
|
||||||
*/
|
*/
|
||||||
handleNavigationParams() {
|
handleNavigationParams() {
|
||||||
this.nextScreen = this.props.route.params.nextScreen;
|
this.nextScreen = this.props.route.params?.nextScreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -121,12 +121,16 @@ function GroupSelectionScreen() {
|
||||||
}
|
}
|
||||||
| undefined
|
| undefined
|
||||||
): Array<{ title: string; data: Array<PlanexGroupCategoryType> }> => {
|
): Array<{ title: string; data: Array<PlanexGroupCategoryType> }> => {
|
||||||
return [
|
if (fetchedData) {
|
||||||
{
|
return [
|
||||||
title: '',
|
{
|
||||||
data: generateData(fetchedData),
|
title: '',
|
||||||
},
|
data: generateData(fetchedData),
|
||||||
];
|
},
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -181,39 +185,34 @@ function GroupSelectionScreen() {
|
||||||
* @returns {[]}
|
* @returns {[]}
|
||||||
*/
|
*/
|
||||||
const generateData = (
|
const generateData = (
|
||||||
fetchedData: PlanexGroupsType | undefined
|
fetchedData: PlanexGroupsType
|
||||||
): Array<PlanexGroupCategoryType> => {
|
): Array<PlanexGroupCategoryType> => {
|
||||||
const data: Array<PlanexGroupCategoryType> = [];
|
const data: Array<PlanexGroupCategoryType> = [];
|
||||||
|
// Convert the object into an array
|
||||||
if (fetchedData) {
|
Object.values(fetchedData).forEach((category: PlanexGroupCategoryType) => {
|
||||||
// Convert the object into an array
|
const content: Array<PlanexGroupType> = [];
|
||||||
Object.values(fetchedData).forEach(
|
// Filter groups matching the search query
|
||||||
(category: PlanexGroupCategoryType) => {
|
category.content.forEach((g: PlanexGroupType) => {
|
||||||
const content: Array<PlanexGroupType> = [];
|
if (stringMatchQuery(g.name, currentSearchString)) {
|
||||||
// Filter groups matching the search query
|
content.push(g);
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
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;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -349,13 +349,7 @@ function ProximoListScreen(props: Props) {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [];
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
data: [],
|
|
||||||
keyExtractor: keyExtractor,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -239,13 +239,7 @@ function ProximoMainScreen() {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
return [
|
return [];
|
||||||
{
|
|
||||||
title: '',
|
|
||||||
data: [],
|
|
||||||
keyExtractor: getKeyExtractor,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import WebSectionList from '../../components/Screens/WebSectionList';
|
||||||
import type { SectionListDataType } from '../../components/Screens/WebSectionList';
|
import type { SectionListDataType } from '../../components/Screens/WebSectionList';
|
||||||
import Urls from '../../constants/Urls';
|
import Urls from '../../constants/Urls';
|
||||||
import { readData } from '../../utils/WebData';
|
import { readData } from '../../utils/WebData';
|
||||||
|
import { REQUEST_STATUS } from '../../utils/Requests';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp<any>;
|
navigation: StackNavigationProp<any>;
|
||||||
|
@ -109,25 +110,31 @@ class SelfMenuScreen extends React.Component<PropsType> {
|
||||||
* @return {[]}
|
* @return {[]}
|
||||||
*/
|
*/
|
||||||
createDataset = (
|
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> => {
|
): SectionListDataType<RuFoodCategoryType> => {
|
||||||
let result: SectionListDataType<RuFoodCategoryType> = [];
|
let result: SectionListDataType<RuFoodCategoryType> = [];
|
||||||
if (fetchedData == null || fetchedData.length === 0) {
|
if (status === REQUEST_STATUS.SUCCESS) {
|
||||||
result = [
|
if (fetchedData == null || fetchedData.length === 0) {
|
||||||
{
|
result = [
|
||||||
title: i18n.t('general.notAvailable'),
|
{
|
||||||
data: [],
|
title: i18n.t('general.notAvailable'),
|
||||||
keyExtractor: this.getKeyExtractor,
|
data: [],
|
||||||
},
|
keyExtractor: this.getKeyExtractor,
|
||||||
];
|
},
|
||||||
} else {
|
];
|
||||||
fetchedData.forEach((item: RawRuMenuType) => {
|
} else {
|
||||||
result.push({
|
fetchedData.forEach((item: RawRuMenuType) => {
|
||||||
title: DateManager.getInstance().getTranslatedDate(item.date),
|
result.push({
|
||||||
data: item.meal[0].foodcategory,
|
title: DateManager.getInstance().getTranslatedDate(item.date),
|
||||||
keyExtractor: this.getKeyExtractor,
|
data: item.meal[0].foodcategory,
|
||||||
|
keyExtractor: this.getKeyExtractor,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue