A state mutation was detected between dispatches, in the path 'auth.friends.0'. This may cause incorrect behavior

Viewed 72

I seen lots of duplicates of this question but non of their solution worked for me. I'm not mutations my friends state, I'm just adding a new chat to another slice. Weirdest part is that this only happens when I create a new chat not when I restart and the chats are loaded.

Here is when the error I think starts InviteScreen.tsx

const InviteScreen: FC<Props> = ({ currentUser, friends }) => {
    const navigation = useNavigation<Stack>();
    const tailwind = useTailwind();

    const [values, setValues] = useState<State>({
        isLoading: false,
        members: [currentUser],
    });

    const handleOnPress = async () => {
        if (values.members.length === 1) return;
        
        const now = serverTimestamp();
        
        // Right here when I create a new chat --------------------------------------------------
        const chatID: string = await ChatsAPI.create({
            ownerID: currentUser.userID,
            title: "Chat Title...",
            membersIDs: values.members.map((u) => u.userID),
            members: values.members.map((u) => ({
                userID: u.userID,
                unreadCount: 0,
                role: currentUser.userID === u.userID ? "admin" : "member",
            })),
            recentMessage: null,
            isLive: true,
            createdAt: now,
            updatedAt: now
        });
        // --------------------------------------------------

        values.members.forEach((member) => {
            UsersAPI.update(member.userID, { isInChat: true });
        })

        if (chatID !== null) {
            navigation.goBack();
            // navigation.navigate("Chat", { chatID });
        }
    };

    const onPress = (user: User) => {
        if (values.members.includes(user)) {
            setValues({ ...values, members: values.members.filter((u) => u.userID !== user.userID) });
        } else {
            setValues({ ...values, members: [...values.members, user] });
        }
    };

    const keyExtractor = (_: any, idx: number) => {
        return idx.toString();
    };


    const renderItem: ListRenderItem<User> = ({ item }) => {
        return <FriendItem friend={item} onPress={() => onPress(item)} />;
    };

    return (
        <ScreenContainer>
            <FlatList<User>
                data={friends}
                renderItem={renderItem}
                keyExtractor={keyExtractor}
                style={tailwind("flex h-full p-3")}
            />
            <View style={tailwind("flex mx-5 justify-end pb-8")}>
                <Button onPress={handleOnPress} text="Start Chatting!" isLoading={false} />
            </View>
        </ScreenContainer>
    );
};

const mapStateToProps = (state: any) => {
    return {
        currentUser: state.auth.currentUser,
        friends: state.auth.friends,
    };
};

const mapDispatchToProps = (dispatch: Dispatch) => {
    return {};
};

export default connect(mapStateToProps, mapDispatchToProps)(InviteScreen);

And when the error actually happens because when I comment it the addChat() out it doesn't crash, but I don't get my chat added...

const DashboardTab: FC<Props> = ({ chats, addChat, updateChat, removeChat }) => {
    const navigation = useNavigation<Stack>();
    const tailwind = useTailwind();

    useEffect(() => {
        const unsubChat = ChatsAPI.existingChat((chat) => {
            console.log("onAdded: " + chat?.chatID);
            addChat(chat!); // <----------- HERE
        }, (chat) => {
            console.log("onModified: " + chat?.chatID);
            // updateChat(chat!);
        }, (chat) => {
            console.log("onRemoved: " + chat?.chatID);
            // removeChat(chat!);
        }, (err) => {
            console.log(err);
        }, () => {
            console.log("Whatever this is...");
        });
    }, []);
    
    ...
}

My chats reducer:

export const chatReducer = (state = initialState, action: AnyAction) => {
  switch (action.type) {
    case TYPES.ADD_CHAT:
      return {
        ...state,
        chats: [...state.chats, action.payload],
      };
    case TYPES.UPDATE_CHAT:
      return {
        ...state,
        chats: state.chats.map((chat: Chat, idx: number) => {
          if (chat.chatID === action.payload.chatID) {
            return action.payload;
          }
          return chat;
        })
      };
    case TYPES.REMOVE_CHAT:
      return {
        ...state,
        chats: state.chats.filter(
          (chat) => chat.chatID !== action.payload.chat.chatID
        ),
      };
    default:
      return { ...state };
  }
};

My auth reducer:

export const authReducer = (state = initialState, action: AnyAction) => {
  switch (action.type) {
    case TYPES.SET_LOGIN_STATUS:
      return {
        ...state,
        isLoggedIn: action.payload,
      };
    case TYPES.SET_CURRENT_USER:
      return {
        ...state,
        currentUser: action.payload,
      };
    case TYPES.NULL_CURRENT_USER:
      return {
        ...state,
        currentUser: action.payload,
      };
    case TYPES.ADD_CONTACTS:
      return {
        ...state,
        contacts: action.payload,
      };
    case TYPES.CLEAR_CONTACTS:
      return {
        ...state,
        contacts: action.payload,
      };
    case TYPES.ADD_FRIENDS:
      return {
        ...state,
        friends: action.payload,
      };
    case TYPES.CLEAR_FRIENDS:
      return {
        ...state,
        friends: action.payload,
      };
    default:
      return { ...state };
  }
};

This only happens when I create a chat, when I restart the app and the same dispatch gets called in when the chats are added it doesn't throw the error...

1 Answers

It is very likely that the object that you put into your state is modified outside of your reducer.

That could be the case if you are directly putting firebase objects into your state - firebase will continue modifying those objects without caring for immutability. You have to create a copy of these objects before putting them into the state by calling myObjectFromFireBase.toJson().

That also makes sure that you put serialize objects into your state - firebase objects themselves can cause problems with redux-persist, some middleware and the devtools.

Also, you should generally be using createSlice and not write switch..case reducers by hand. That will often (but not always, in the case of firebase objects) prevent these errors from happening. Also, it is generally recommended since 2019.

Related