Reducer code gets run first time, but consecutive times only Actions code is executed and Reducer code is not? | React-Native and Redux

Viewed 97

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);
1 Answers

I finally figured it out, after coming back from a walk with the little one, and wanted to share the cause of the error with you guys for anyone else running into this issue... :)

I was experimenting with creating a duplicate mutable state array, and ended up with this line:

const updatingAvailableProfiles = {...state.availableProfiles};

after setting it back to

const updatingAvailableProfiles = state.availableProfiles;

it works as expected again.

Related