Ошибка, вызванная оператором распространения в реактивном приложении с уменьшением

#arrays #reactjs #react-native #redux #redux-thunk

#массивы #reactjs #react-native #уменьшение #redux-thunk

Вопрос:

Я создаю клон Instagram с expo, используя redux и redux thunk, я хотел внедрить систему подписки, но при переключении на экран профиля я получаю сообщение об ошибке, указывающее на мои редукторы.

users.ts (reducer) :

 import {
  USER_STATE_CHANGE,
  USER_POSTS_STATE_CHANGE,
  USER_FOLLOWING_STATE_CHANGE,
  CLEAR_DATA,
} from "../constants";

const initialState = {
  currentUser: null,
  posts: [],
  following: [],
};

export const user = (state = initialState, action: any) => {
  switch (action.type) {
    case USER_STATE_CHANGE:
      return {
        ...state,
        currentUser: action.currentUser,
      };
    case USER_POSTS_STATE_CHANGE:
      return {
        ...state,
        posts: action.posts,
      };
    case USER_FOLLOWING_STATE_CHANGE:
      return {
        ...state,
        following: action.following,
      };
    case CLEAR_DATA:
      return {
        initialState,
      };

    default:
      return state;
  }
};
 

Я получаю следующую ошибку :

Ошибка типа: недопустимая попытка распространения не итерируемого экземпляра. Чтобы быть итеративными, объекты, не являющиеся массивами, должны иметь метод Symbol.iterator .

user.ts (управляет пользователем) :

 import {
  USER_STATE_CHANGE,
  USER_POSTS_STATE_CHANGE,
  USER_FOLLOWING_STATE_CHANGE,
  CLEAR_DATA,
} from "../constants";

const initialState = {
  currentUser: null,
  posts: [],
  following: [],
};

export const user = (state = initialState, action: any) => {
  switch (action.type) {
    case USER_STATE_CHANGE:
      return {
        ...state,
        currentUser: action.currentUser,
      };
    case USER_POSTS_STATE_CHANGE:
      return {
        ...state,
        posts: action.posts,
      };
    case USER_FOLLOWING_STATE_CHANGE:
      return {
        ...state,
        following: action.following,
      };
    case CLEAR_DATA:
      return {
        initialState,
      };

    default:
      return state;
  }
};

 

Profile.tsx (код экрана профиля) :

 import React, { useState, useEffect } from "react";
import { View, Text, StyleSheet, Image, FlatList, Alert } from "react-native";
import db, { auth } from "../../firebase";
import { Avatar, Button, ActivityIndicator, Colors } from "react-native-paper";
import Navbar from "../shared/Navbar";
import { connect } from "react-redux";

const Profile = ({
  navigation,
  posts,
  route,
  reduxFollowing,
  currentUser,
}: any) => {
  const [user, setUser] = useState<any>(null);
  const [userPosts, setUserPosts] = useState<any>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [following, setFollowing] = useState<boolean>(false);

  useEffect(() => {
    if (route.params.uid === auth.currentUser?.uid) {
      setUser(auth.currentUser);
      setUserPosts(posts);
      setLoading(false);
    } else {
      db.collection("users")
        .doc(route.params?.uid)
        .get()
        .then((snapshot) => {
          setLoading(false);
          if (snapshot.exists) {
            setUser(snapshot.data());
          } else {
            console.log("does not exist");
          }
        });

      db.collection("posts")
        .doc(route.params.uid)
        .collection("userPosts")
        .orderBy("creation", "desc")
        .onSnapshot((snapshot) => {
          let posts = snapshot.docs.map((doc) => {
            const data = doc.data();
            const id = doc.id;
            //Array.from(data)
            console.log(data);
            //return { ...data, id };
          });
          //setUserPosts(posts);
        });

      if (reduxFollowing.indexOf(route.params.uid) > -1) {
        setFollowing(true);
      } else {
        setFollowing(false);
      }
    }
  }, [route.params.uid, reduxFollowing]);

  const onFollow = () => {
    db.collection("following")
      .doc(auth.currentUser?.uid)
      .collection("userFollowing")
      .doc(route.params.uid)
      .set({})
      .then(() => setFollowing(true))
      .catch((err) =>
        Alert.alert("Opps!, could not Login", err.message, [{ text: "Ok" }])
      );
  };

  const onUnfollow = () => {
    db.collection("following")
      .doc(auth.currentUser?.uid)
      .collection("userFollowing")
      .doc(route.params.uid)
      .delete()
      .then(() => setFollowing(false))
      .catch((err) =>
        Alert.alert("Opps!, could not Login", err.message, [{ text: "Ok" }])
      );
  };

  if (loading) {
    return (
      <View style={styles.loading}>
        <ActivityIndicator size={60} color={Colors.blue500} />
      </View>
    );
  }

  const TwoBtn = () => (
    <>
      {following ? (
        <Button
          style={styles.btn}
          uppercase={false}
          mode="outlined"
          color={Colors.green500}
          onPress={onUnfollow}
        >
          Following
        </Button>
      ) : (
        <Button
          style={styles.btn}
          uppercase={false}
          mode="outlined"
          color="black"
          onPress={onFollow}
        >
          Follow
        </Button>
      )}
      <Button
        style={styles.btn}
        uppercase={false}
        mode="outlined"
        color="black"
        onPress={() => navigation.navigate("Chat")}
      >
        Chat
      </Button>
    </>
  );

  const OneBtn = () => (
    <Button
      style={styles.btn2}
      uppercase={false}
      mode="outlined"
      color="black"
      onPress={() => console.log("")}
    >
      Edit Profile
    </Button>
  );

  return (
    <View style={styles.container}>
      <Navbar
        navigation={navigation}
        title={auth.currentUser?.displayName || currentUser.name}
      />

      <View style={styles.topContainer}>
        <Avatar.Image
          source={user?.photoURL || require("../../assets/Avatar.png")}
        />
        <View style={styles.topRightCont}>
          <Text style={styles.label}>
            Name :{" "}
            <Text style={styles.text}>{user?.displayName || user?.name}</Text>
          </Text>
          <Text style={styles.label}>
            Email : <Text style={styles.text}>{user?.email}</Text>
          </Text>
        </View>
      </View>

      {route.params.uid === auth.currentUser?.uid ? (
        <View style={styles.btnCont}>
          <OneBtn />
        </View>
      ) : (
        <View style={styles.btnCont}>
          <TwoBtn />
        </View>
      )}

      <View style={styles.galleryCont}>
        <FlatList
          keyExtractor={({ item }) => item?.id}
          numColumns={3}
          horizontal={false}
          data={userPosts}
          renderItem={({ item }) => (
            <Image style={styles.image} source={{ uri: item?.downloadURL }} />
          )}
        />
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
//styles
});

const mapStateToProps = (store: any) => ({
  currentUser: store.userState.currentUser,
  posts: store.userState.posts,
  reduxFollowing: store.userState.following,
});

export default connect(mapStateToProps, null)(Profile);
 

Я не знаю, важно ли это, я следую руководству YouTube (ссылка на руководство)

Редактировать :

Это действие, которое отправляет USERS_DATA_STATE_CHANGE

 export function getUsersData(uid: any) {
  return (dispatch: any, getState: any) => {
    const found = getState().usersState.users?.some(
      (el: any) => el.uid === uid
    );
    if (!found) {
      db.collection("users")
        .doc(uid)
        .onSnapshot((snapshot) => {
          if (snapshot.exists) {
            let user = snapshot.data();
            if (user) {
              user.uid = snapshot.id;
              dispatch({
                type: USERS_DATA_STATE_CHANGE,
                user,
              });
              dispatch(fetchUsersFollowingPosts(uid));
            }
          } else {
            console.log("does not exist");
          }
        });
    }
  };
}
 

Это функция, которую я там использую (она находится в файле действий) :

 export function fetchUsersFollowingPosts(uid: any) {
  return (dispatch: any, getState: any) => {
    db.collection("posts")
      .doc(uid)
      .collection("userPosts")
      .orderBy("creation", "desc")
      .onSnapshot((snapshot) => {
        const uid = snapshot.query.EP.path.segments[1];
        const user = getState().usersState.users.find(
          (el: any) => el.uid === uid
        );
        let posts = snapshot.docs.map((doc) => {
          const id = doc.id;
          const data = doc.data();
          return { id, ...data, user };
        });
        dispatch({
          type: USERS_POSTS_STATE_CHANGE,
          posts,
          uid,
        });
      });
  };
}

 

Комментарии:

1. users: [...state.users, action.user] —> доступ user через payload prop … Экшен. полезная нагрузка … users: [...state.users, action.payload] … и если бы вы могли включить создателя действия, который отправляет USERS_DATA_STATE_CHANGE , это было бы неплохо

2. Я включил код, который вы просили, плюс спасибо за ваше предложение, это предотвратило выдачу ошибки приложением еще на некоторое время, но она пока не появляется

3. ОБНОВЛЕНИЕ: та же ошибка появляется и на главном экране, но указывает на ту же строку

4. я чувствую, что здесь отсутствует еще более релевантный код. state.users что-то должно где-то измениться, чтобы вызвать это. опубликуйте другие редукторы

5. также не делайте этого — dispatch(fetchUsersFollowingPosts(uid)); функция fetchUsers … возвращает не объект действия, а другую функцию. не понимаю, как это может вызвать вашу проблему, просто нужно исключить еще одну вещь

Ответ №1:

Во-первых, вам initialState , похоже, не хватает users: [] пары prop / val, которая была у него раньше — нужно добавить ее обратно.

Затем в вашем редукторе вы возвращаете здесь новый объект:

 case CLEAR_DATA:
  return {
    initialState,
  };
 

Это необходимо распространить:

 case CLEAR_DATA:
  return {
    ...initialState
  };
 

в противном случае это становится просто { initialState: { users: [], ...etc } } вместо { users: [], ...etc } .

Комментарии:

1. Или это может быть просто case CLEAR_DATA: return initialState

2. технически да, но обычно вы хотите каждый раз предоставлять новое состояние, чтобы предотвратить неожиданную мутацию в будущем. если вы просто вернетесь initialState , то где-то вы мутируете его напрямую с getState().usersState.new_value = true помощью now new_value: true , при каждом последующем CLEAR_DATA действии. документы для получения более подробной информации — redux.js.org/tutorials/fundamentals /…

3. Спасибо, что помогли мне, но это создает еще одну ошибку: TypeError: Cannot read property 'uid' of undefined и она указывает на USERS_POSTS_STATE_CHANGE

4. поместите точку останова над этой строкой и посмотрите, каковы значения, узнайте, что вызвало функцию, чтобы доставить вас туда, и отследите, где она отсутствует

5. @Deryck Спасибо, но прямое изменение состояния, выполняя что-то подобное getState().usersState.new_value = true , очень необычно… обычно мы берем свежую новую копию и играем с ней… return initialState похоже на то return state; , что мы вызываем default switch case