I usually try to solve problems by myself, so I learn more from it, by checking StackOverflow and Google, but this one has me stuck for too long now...
CONTEXT
So, I'm creating a React-Native app (no Expo) and use redux, redux-thunk and react-redux.
There's a feature where the logged in user is supposed to be able to enter or edit his/her availability for future dates. So, I have dummy data in place to set the initial values.
I can successfully edit any of the dates (using availability-day.component.tsx) and show the updated availability on the User Profile Screen (profile.component.tsx).
PROBLEM
However, when I try to edit any other date after that first edit, it will run my code up to the end of the respective redux actions code, but will not execute the respective redux reducer code (while it did run that code at the first edit)...
#TLDR I can edit and update an existing availability once, but reducer code is not executed with consecutive edits
Any ideas what causes this and how to solve it?
#FILES
Relevant Files on Google Drive
I couldn't add all the files in my question or comments below due to characters limit, so stored them on Google Drive.
// reducer action snippet
export const updateAvailability = (
userId,
userAccountType,
userFirstName,
userLastName,
userEmail,
userPhoneNumber,
userAvatar,
userAddress,
userLocation,
userBio,
userFollowers,
userFollowing,
userPosts,
updatedUserAvailability,
userGender,
userAge,
) => {
return async (dispatch) => {
const date = new Date();
console.log('ARRIVED AT START OF USERS ACTIONS');
console.log(updatedUserAvailability);
try {
dispatch({
type: UPDATE_AVAILABILITY,
profileData: {
userId: userId,
userAccountType: userAccountType,
userFirstName: userFirstName,
userLastName: userLastName,
userEmail: userEmail,
userPhoneNumber: userPhoneNumber,
userAvatar: userAvatar,
userAddress: userAddress,
userLocation: userLocation,
userBio: userBio,
userFollowers: userFollowers,
userFollowing: userFollowing,
userPosts: userPosts,
userAvailability: updatedUserAvailability,
userGender: userGender,
userAge: userAge,
},
});
} catch (err) {
// send to custom analytics server
throw err;
}
};
};
// redux reducer snippet
import {
GET_PROFILES,
CREATE_PROFILE,
UPDATE_PROFILE,
UPDATE_AVAILABILITY,
} from '../actions/users';
const initialState = {
availableProfiles: PROFILES,
activeProfile: PROFILES.find((profile) => profile.userId === 'u2'),
error: null,
};
const usersReducer = (state = initialState, action) => {
switch (action.type) {
case UPDATE_AVAILABILITY:
console.log('ARRIVED AT START REDUCER');
// Find and update the activeUser Profile.
const updatingProfileIndex = state.availableProfiles.findIndex(
(profile) => profile.userId === action.profileData.userId,
);
console.log(updatingProfileIndex);
console.log(action.profileData.userAvailability);
const updatedAvailabilityProfile = new Profile(
action.profileData.userId,
action.profileData.userAccountType,
action.profileData.userFirstName,
action.profileData.userLastName,
action.profileData.userEmail,
action.profileData.userPhoneNumber,
action.profileData.userAvatar,
action.profileData.userAddress,
action.profileData.userLocation,
action.profileData.userBio,
action.profileData.userFollowers,
action.profileData.userFollowing,
action.profileData.userPosts,
action.profileData.userAvailability,
action.profileData.userGender,
action.profileData.userAge,
);
console.log(updatedAvailabilityProfile);
console.log(
'UPDATED AVAILABILITIY PROFILE=' +
JSON.stringify(updatedAvailabilityProfile),
);
// const updatedActiveProfile = [...state.activeProfile];
// console.log(updatedActiveProfile);
// updatedActiveProfile[profileIndex] = updatedProfile;
// console.log(updatedActiveProfile);
// Update the activeUser Profile in the availableProfiles array.
const updatingAvailableProfiles = {...state.availableProfiles};
updatingAvailableProfiles[
updatingProfileIndex
] = updatedAvailabilityProfile;
// Return the updated profile states
return {
...state,
availableProfiles: updatingAvailableProfiles,
activeProfile: updatedAvailabilityProfile,
};
default:
return state;
}
};
export default usersReducer;
// editing component that sends updated values to redux
import React from 'react';
import {View} from 'react-native';
import {
Avatar,
Button,
StyleService,
Text,
TopNavigation,
TopNavigationAction,
useStyleSheet,
useTheme,
Modal,
Card,
Datepicker,
Spinner,
} from '@ui-kitten/components';
import * as usersActions from '../../store/actions/users';
import {Availability, Profile} from '../../data/profile.model';
import {brandingAppIcon} from '../../assets/images/branding';
import {Timepicker} from '../../components/UI/time-picker.component';
import {AvailabilityDayEditScreenProps} from '../../navigation/profile.navigator';
import {useSelector, connect, useDispatch} from 'react-redux';
import {AppRoute} from '../../navigation/app-routes';
export type AvailabilityDayEditRouteParams = {
editableAvailability: number;
};
export const AvailabilityDayEditScreen = (
props: AvailabilityDayEditScreenProps,
): SafeAreaLayoutElement => {
const {editableAvailability} = props.route.params;
console.log(editableAvailability);
const currentUser: Profile = useSelector(
(state) => state.users.activeProfile,
);
console.log(currentUser);
const [isLoading, setIsLoading] = React.useState(false);
const [error, setError] = React.useState();
const [activeUser, setActiveUser] = React.useState<Profile>(currentUser);
const [userAvailability, setUserAvailability] = React.useState<
Availability[]
>(activeUser.userAvailability);
const [userAvailabilityEditing, setUserAvailabilityEditing] = React.useState<
Object
>(activeUser.userAvailability[editableAvailability]);
const [editDate, setEditDate] = React.useState<Date>(
userAvailabilityEditing.date,
);
const [editDateFrom, setEditDateFrom] = React.useState<Date>(
userAvailabilityEditing.from,
);
const [editDateTo, setEditDateTo] = React.useState<Date>(
userAvailabilityEditing.to,
);
React.useEffect(() => {
setActiveUser(currentUser);
setUserAvailability(currentUser.userAvailability);
}, [currentUser]);
const dispatch = useDispatch();
const submitHandler = React.useCallback(async () => {
// if (!formState.formIsValid) {
// Alert.alert('Wrong input!', 'Please check the errors in the form.', [
// {text: 'OK'},
// ]);
// return;
// }
setError(null);
setIsLoading(true);
try {
console.log(userAvailabilityEditing);
// console.log('EDITDATE=' + editDate);
// console.log('EDITFROM=' + editDateFrom);
// console.log('EDITTO=' + editDateTo);
const newUserAvailabilityEditing = {
...userAvailabilityEditing,
from: editDateFrom,
to: editDateTo,
};
console.log(newUserAvailabilityEditing); // CORRECT
// setEditDate(undefined); // reset editDate
const updatedUserAvailability = userAvailability.map((availItem) => {
if (userAvailability.indexOf(availItem) === editableAvailability) {
return newUserAvailabilityEditing;
}
return availItem;
});
console.log('UPDATED USER AVAILABILITY BELOW');
console.log(updatedUserAvailability); // CORRECT
// console.log(activeUser);
if (activeUser) {
await dispatch(
usersActions.updateAvailability(
activeUser.userId,
activeUser.userAccountType,
activeUser.userFirstName,
activeUser.userLastName,
activeUser.userEmail,
activeUser.userPhoneNumber,
activeUser.userAvatar,
activeUser.userAddress,
activeUser.userLocation,
activeUser.userBio,
activeUser.userFollowers,
activeUser.userFollowing,
activeUser.userPosts,
updatedUserAvailability,
activeUser.userGender,
activeUser.userAge,
),
);
}
} catch (err) {
setError(err.message);
}
setIsLoading(false);
console.log('Sending User Back to Profile Screen');
props.navigation.navigate(AppRoute.PROFILE);
}, [
props.navigation,
userAvailabilityEditing,
editDateFrom,
editDateTo,
userAvailability,
activeUser,
editableAvailability,
dispatch,
]);
if (isLoading) {
return (
<View style={styles.centeredLoader}>
<Spinner size="large" />
</View>
);
}
return (
<Card
disabled={true}
style={styles.container}
header={() => (
<View>
<Avatar style={styles.profileAvatar} source={brandingAppIcon} />
</View>
)}>
<Text>Please edit your availability for this date.</Text>
<View style={styles.availabilityDayEdit}>
<Datepicker date={editDate} disabled />
<Timepicker
key={'editStartTime'}
placeholder="Available to start at"
date={editDateFrom}
onSelect={(nextTime) => {
setEditDateFrom(nextTime);
}}
/>
<Timepicker
key={'editEndTime'}
placeholder="Available until"
date={editDateTo}
onSelect={(nextTime) => {
setEditDateTo(nextTime);
}}
/>
</View>
<Text>{error}</Text>
<Button onPress={submitHandler}>UPDATE AVAILABILITY</Button>
</Card>
);
};
const styles = StyleService.create({
safeArea: {
flex: 1,
},
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
// backgroundColor: 'background-basic-color-2',
},
appName: {
color: 'text-control-color',
},
header: {
flex: 1,
paddingVertical: 24,
alignItems: 'center',
justifyContent: 'center',
},
profileAvatar: {
width: 124,
height: 124,
alignSelf: 'center',
borderRadius: 62,
marginVertical: 16,
},
profileName: {
zIndex: 1,
},
locationContainer: {
flexDirection: 'row',
alignItems: 'center',
},
location: {
marginVertical: 8,
},
profileButtonsContainer: {
flexDirection: 'row',
marginVertical: 32,
marginHorizontal: 20,
},
profileButton: {
flex: 1,
marginHorizontal: 4,
},
socialsContainer: {
flexDirection: 'row',
width: '75%',
marginVertical: 8,
},
profileSocial: {
flex: 1,
alignSelf: 'center',
},
sectionLabel: {
marginTop: 16,
marginBottom: 8,
marginHorizontal: 16,
},
profileDescription: {
marginHorizontal: 16,
},
friendsList: {
marginHorizontal: 8,
},
friendItem: {
alignItems: 'center',
marginHorizontal: 8,
},
friendName: {
marginTop: 8,
},
postItem: {
flex: 1,
aspectRatio: 1.0,
},
availabilityContainer: {
marginLeft: 16,
marginRight: -16,
},
availabilityItemLabel: {
flex: 1,
textDecorationLine: 'underline',
},
availabilityItemValue: {
flex: 1,
},
availabilityItem: {
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
marginVertical: 8,
},
availabilityItemDate: {
flex: 3,
},
availabilityItemTime: {
flex: 1,
},
availabilityItemButton: {
// flex: 1,
marginRight: 30,
},
addVenueItem: {
marginHorizontal: 8,
alignItems: 'center',
marginLeft: 0,
},
addVenueButton: {
aspectRatio: 1.0,
height: 42,
borderRadius: 24,
color: 'text-control-color',
paddingRight: 7,
// resizeMode: 'contain',
// width: 50,
// height: 50,
// borderRadius: 50,
// backgroundColor:'black'
},
addVenueButtonText: {
paddingTop: 5,
fontSize: 13,
},
modalBackdrop: {
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
availabilityDayEdit: {
flexDirection: 'row',
paddingVertical: 16,
justifyContent: 'space-between',
},
});
connect()(AvailabilityDayEditScreen);