🥅 Handle fetch error callbacks

pull/11/head
Alexis Drai 2 years ago
parent da02d7f543
commit 1063026e2d

@ -0,0 +1,61 @@
// components/AlertModal.tsx
import { Button, Modal, StyleSheet, Text, View } from 'react-native';
import React from 'react';
type AlertModalProps = {
visible: boolean;
message: string;
onClose: () => void;
};
const AlertModal = ({ visible, message, onClose }: AlertModalProps) => {
return (
<Modal
animationType="slide"
transparent={true}
visible={visible}
onRequestClose={onClose}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.modalText}>{message}</Text>
<Button
title="Close"
onPress={onClose}
/>
</View>
</View>
</Modal>
);
};
const styles = StyleSheet.create({
centeredView: {
flex: 1,
justifyContent: "center",
alignItems: "center",
marginTop: 16
},
modalView: {
margin: 16,
backgroundColor: "white",
borderRadius: 16,
padding: 32,
alignItems: "center",
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5
},
modalText: {
marginBottom: 16,
textAlign: "center"
}
});
export default AlertModal;

@ -20,10 +20,8 @@ const MoveListItem: React.FC<MoveListItemProps> = ({ move, onPress, style }) =>
const styles = StyleSheet.create({
listItem: {
backgroundColor: '#DDD',
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 10,
padding: 8,
borderRadius: 8,
},
listItemText: {
color: '#333',

@ -1,71 +1,105 @@
// redux/actions/moveAction.ts
import { CREATE_MOVE, DELETE_MOVE, GET_MOVES, UPDATE_MOVE } from '../constants';
import { Move } from "../../entities/Move";
import { Dispatch } from "redux";
import { API_BASE_URL } from "../../config";
import { CREATE_MOVE, DELETE, DELETE_MOVE, GET, GET_MOVES, MOVE_ERROR, POST, PUT, UPDATE_MOVE } from '../constants';
import {
Move
} from "../../entities/Move";
import { Dispatch } from "redux";
import { API_BASE_URL } from "../../config";
export const createMove = (move: Move) => {
const verb = POST
return async (dispatch: Dispatch) => {
try {
const response = await fetch(`${API_BASE_URL}/move`, {
method: 'POST',
method: verb,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(move),
});
if (!response.ok) {
throw new Error(`Failed to ${verb}: ${response.statusText}`);
}
const data = await response.json();
dispatch({ type: CREATE_MOVE, payload: data });
}
catch (error) {
console.error(error);
// @ts-ignore
dispatch({ type: MOVE_ERROR, payload: error.message });
}
}
}
export const getMoves = () => {
const verb = GET
return async (dispatch: Dispatch) => {
try {
const response = await fetch(`${API_BASE_URL}/move`);
if (!response.ok) {
throw new Error(`Failed to ${verb}: ${response.statusText}`);
}
const data = await response.json();
dispatch({ type: GET_MOVES, payload: data });
}
catch (error) {
console.error(error);
// @ts-ignore
dispatch({ type: MOVE_ERROR, payload: error.message });
}
}
}
export const updateMove = (id: string, move: Move) => {
const verb = PUT
return async (dispatch: Dispatch) => {
try {
const response = await fetch(`${API_BASE_URL}/move/${id}`, {
method: 'PUT',
method: verb,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(move),
});
if (!response.ok) {
throw new Error(`Failed to ${verb}: ${response.statusText}`);
}
const updatedMove = await response.json();
dispatch({ type: UPDATE_MOVE, payload: updatedMove });
}
catch (error) {
console.error(error);
// @ts-ignore
dispatch({ type: MOVE_ERROR, payload: error.message });
}
}
}
export const deleteMove = (id: string) => {
const verb = DELETE
return async (dispatch: Dispatch) => {
try {
await fetch(`${API_BASE_URL}/move/${id}`, {
method: 'DELETE',
const response = await fetch(`${API_BASE_URL}/move/${id}`, {
method: verb,
});
if (!response.ok) {
throw new Error(`Failed to ${verb}: ${response.statusText}`);
}
dispatch({ type: DELETE_MOVE, payload: id });
}
catch (error) {
console.error(error);
// @ts-ignore
dispatch({ type: MOVE_ERROR, payload: error.message });
}
}
}

@ -1,6 +1,12 @@
// redux/constants.ts
export const GET = 'GET';
export const PUT = 'PUT';
export const POST = 'POST';
export const DELETE = 'DELETE';
export const GET_MOVES = 'GET_MOVES';
export const CREATE_MOVE = 'CREATE_MOVE';
export const UPDATE_MOVE = 'UPDATE_MOVE';
export const DELETE_MOVE = 'DELETE_MOVE';
export const MOVE_ERROR = 'MOVE_ERROR';

@ -1,9 +1,10 @@
// redux/reducers/moveReducer.ts
import { CREATE_MOVE, DELETE_MOVE, GET_MOVES, UPDATE_MOVE } from '../constants';
import { Move } from "../../entities/Move";
import { CREATE_MOVE, DELETE_MOVE, GET_MOVES, MOVE_ERROR, UPDATE_MOVE } from '../constants';
import { Move } from "../../entities/Move";
export type MoveState = {
moves: Move[];
error: string | null;
};
type MoveAction = {
@ -13,27 +14,37 @@ type MoveAction = {
const initialState: MoveState = {
moves: [],
error: null
}
export default function moveReducer(state = initialState, action: MoveAction): MoveState {
switch (action.type) {
case GET_MOVES:
return {
...state, moves: action.payload as Move[] || []
...state, moves: action.payload as Move[] || [],
error: null,
};
case CREATE_MOVE:
return {
...state, moves: [...state.moves, action.payload as Move]
...state, moves: [...state.moves, action.payload as Move],
error: null,
};
case UPDATE_MOVE:
return {
...state,
moves: state.moves.map(move => move.id === (action.payload as Move).id ? action.payload as Move : move)
moves: state.moves.map(move => move.id === (action.payload as Move).id ? action.payload as Move : move),
error: null,
};
case DELETE_MOVE:
return {
...state,
moves: state.moves.filter(move => move.id !== action.payload)
moves: state.moves.filter(move => move.id !== action.payload),
error: null,
};
case MOVE_ERROR:
return {
...state,
error: action.payload as string
};
default:
return state;

@ -4,9 +4,9 @@ import React, { useState } from 'react';
import { Button, StyleSheet, Text, TextInput } from 'react-native';
import { StackNavigationProp } from '@react-navigation/stack';
import { RootStackParamList } from "../../navigation/navigationTypes";
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { createMove, updateMove } from '../../redux/actions/moveActions';
import { AppDispatch } from "../../redux/store";
import { AppDispatch, RootState } from "../../redux/store";
import { Move } from "../../entities/Move";
import { RouteProp } from "@react-navigation/native";
import { MOVE_FORM } from "../../navigation/constants";
@ -16,6 +16,8 @@ import { MoveCategoryName } from "../../entities/MoveCategory
import { TypeName } from "../../entities/TypeName";
import MultiSelect from "react-native-multiple-select";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import AlertModal from "../../components/AlertModal";
import { MOVE_ERROR } from "../../redux/constants";
type MoveFormScreenNavigationProp = StackNavigationProp<RootStackParamList, typeof MOVE_FORM>;
type MoveFormScreenRouteProp = RouteProp<RootStackParamList, typeof MOVE_FORM>;
@ -26,6 +28,7 @@ type Props = {
};
const MoveFormScreen = ({ navigation, route }: Props) => {
const error = useSelector((state: RootState) => state.move.error);
const dispatch = useDispatch();
const [move, setMove] = useState<Move>(route.params?.move || {
id: null,
@ -41,7 +44,6 @@ const MoveFormScreen = ({ navigation, route }: Props) => {
schemaVersion: 2
});
const [selectedWeakAgainst, setSelectedWeakAgainst] = useState<string[]>(move.type.weakAgainst);
const [selectedEffectiveAgainst, setSelectedEffectiveAgainst] = useState<string[]>(move.type.effectiveAgainst);
@ -69,6 +71,11 @@ const MoveFormScreen = ({ navigation, route }: Props) => {
return (
<KeyboardAwareScrollView style={styles.container}>
<AlertModal
visible={!!error}
message={error || ''}
onClose={() => dispatch({ type: MOVE_ERROR, payload: null })}
/>
<Text style={styles.label}>Name: </Text>
<TextInput
value={move.name}

@ -11,6 +11,8 @@ import { AppDispatch } from "../../redux/store";
import MoveListItem from "../../components/MoveListItem";
import { MOVE_DETAIL, MOVE_FORM, MOVE_LIST } from "../../navigation/constants";
import { RouteProp, useFocusEffect } from "@react-navigation/native";
import AlertModal from "../../components/AlertModal";
import { MOVE_ERROR } from "../../redux/constants";
type MoveListScreenNavigationProp = StackNavigationProp<RootStackParamList, typeof MOVE_LIST>;
type MoveListScreenRouteProp = RouteProp<RootStackParamList, typeof MOVE_LIST>;
@ -24,6 +26,7 @@ type RootState = {
};
const MoveListScreen = ({ navigation }: Props) => {
const error = useSelector((state: RootState) => state.move.error);
const dispatch: AppDispatch = useDispatch();
const moves = useSelector((state: RootState) => state.move.moves);
@ -37,29 +40,36 @@ const MoveListScreen = ({ navigation }: Props) => {
);
return (
<FlatList
data={moves}
ListHeaderComponent={
<Button title="Add Move" onPress={() => navigation.navigate(MOVE_FORM, { move: undefined })}/>
}
renderItem={({ item }) => (
<View style={styles.listItemContainer}>
<MoveListItem
move={item}
style={styles.moveListItem}
onPress={() => navigation.navigate(MOVE_DETAIL, { move: item })}
/>
<Button title="X"
color={styles.deleteButton.backgroundColor}
onPress={() => {
if (item.id) {
dispatch(deleteMove(item.id))
}
}}/>
</View>
)}
keyExtractor={(item) => item.name}
/>
<>
<AlertModal
visible={!!error}
message={error || ''}
onClose={() => dispatch({ type: MOVE_ERROR, payload: null })}
/>
<FlatList
data={moves}
ListHeaderComponent={
<Button title="Add Move" onPress={() => navigation.navigate(MOVE_FORM, { move: undefined })}/>
}
renderItem={({ item }) => (
<View style={styles.listItemContainer}>
<MoveListItem
move={item}
style={styles.moveListItem}
onPress={() => navigation.navigate(MOVE_DETAIL, { move: item })}
/>
<Button title="X"
color={styles.deleteButton.backgroundColor}
onPress={() => {
if (item.id) {
dispatch(deleteMove(item.id))
}
}}/>
</View>
)}
keyExtractor={(item) => item.name}
/>
</>
);
};
@ -71,10 +81,12 @@ const styles = StyleSheet.create({
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 16,
marginTop: 8,
},
moveListItem: {
flex: 1,
marginRight: 10
marginRight: 8
}
});

Loading…
Cancel
Save