【问题标题】:Fetch the data from firestore in the User Profile从用户配置文件中的 firestore 获取数据
【发布时间】:2021-04-17 11:57:11
【问题描述】:

我有一个小问题。我为书店市场制作了一个应用程序。现在,每个用户的书都在 Market Screen 上发布。在个人资料屏幕中,数据会更新(姓名、个人资料图像、关于等)。当需要在用户的个人资料屏幕中查看用户的书籍时,问题就出现了。在每个个人资料屏幕上,都会显示当前用户发布的相同书籍。

MarketScreen 代码:

export default function MarketScreen({navigation, route}) {
  const [books, setBooks] = useState([]);
  const [loading, setLoading] = useState(true);
  const [query, setQuery] = useState('');
  const [deleted, setDeleted] = useState(false);
  const user = firebase.auth().currentUser;
  // const userId = firebase.auth().currentUser.uid;
  const [userData, setUserData] = useState(null);

  const fetchBook = async () => {
    try {
      const list = [];

      await firestore()
        .collection('user_book')
        .orderBy('postTime', 'desc')
        .get()
        .then(querySnapshot => {
          // console.log('Total books from users', querySnapshot.size);
          querySnapshot.forEach(doc => {
            const {
              userId,
              title,
              bookImg,
              postTime,
              author,
              genre,
              summary,
              price,
            } = doc.data();
            list.push({
              id: doc.id,
              userId,
              title,
              bookImg,
              postTime: postTime,
              author,
              genre,
              summary,
              price,
            });
          });
        });

      setBooks(list);
      if (loading) {
        setLoading(false);
      }

      console.log('Books', list);
    } catch (e) {
      console.log(e);
    }
  };

  const getUser = async () => {
    const currentUser = await firestore()
      .collection('user')
      .doc(user.uid)
      .get()
      .then(documentSnapshot => {
        if (documentSnapshot.exists) {
          console.log('User Data', documentSnapshot.data());
          setUserData(documentSnapshot.data());
        }
      });
  };

  useEffect(() => {
    getUser();
  }, []);

  useEffect(() => {
    fetchBook();
  }, []);

  useEffect(() => {
    getUser();
    fetchBook();
    setDeleted(false);
  }, [deleted]);

  const deleteBook = bookId => {
    console.log(bookId);

    firestore()
      .collection('user_book')
      .doc(bookId)
      .get()
      .then(documentSnapshot => {
        if (documentSnapshot.exists) {
          const {bookImg} = documentSnapshot.data();

          if (bookImg !== null) {
            const sotageRef = storage().refFromURL(bookImg);
            const imageRef = storage().ref(sotageRef.fullPath);

            imageRef
              .delete()
              .then(() => {
                console.log('${bookImg} has been deleted');
                deleteFirestoreData(bookId);
                setDeleted(true);
              })
              .catch(e => {
                console.log('Error while deleting the image', e);
              });
          } else {
            deleteFirestoreData(bookId);
          }
        }
      });
  };

  const handleDelete = bookId => {
    Alert.alert('Delete Book', 'Are you sure?', [
      {
        text: 'Cancel',
        onPress: () => console.log('Cancel Pressed'),
        style: 'cancel',
      },
      {text: 'Confirm', onPress: () => deleteBook(bookId)},
    ]);
  };

  const deleteFirestoreData = bookId => {
    firestore()
      .collection('user_book')
      .doc(bookId)
      .delete()
      .then(() => {
        Alert.alert('Your book was deleted from MarketStore');
      })
      .catch(e => console.log('Error deleting book', e));
  };

  const ItemSeparatorView = () => {
    return (
      <View style={{height: 1, width: '100%', backgroundColor: 'black'}}></View>
    );
  };

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={books}
        extraData={query}
        ItemSeparatorVie={ItemSeparatorView}
        keyExtractor={(index, item) => item.toString()}
        renderItem={({item}) => (
          <TouchableOpacity
            style={styles.bookFeed}
            onPress={() => navigation.navigate('UserBook', {item})}>
            <View style={styles.textViewStyle}>
              <Text style={styles.txt1}>
                Posted by:
                <TouchableOpacity
                  onPress={() =>
                    navigation.navigate('Profile', {userId: item.userId})
                  }>
                  <Text style={styles.txtUser}>{item.userId}</Text>
                </TouchableOpacity>
              </Text>
              <Text style={styles.txt1}>
                Title:
                <Text style={styles.txt2}>{item.title}</Text>
              </Text>
              <Text style={styles.txt1}>
                Author:<Text style={styles.txt2}>{item.author}</Text>
              </Text>
              <Text style={styles.txt1}>
                Genre:<Text style={styles.txt2}>{item.genre}</Text>
              </Text>
              <Text style={styles.txt1}>
                Price:<Text>{item.price} lei</Text>
              </Text>
              <View style={{flexDirection: 'row'}}>
                <Text>{moment(item.postTime.toDate()).fromNow()}</Text>
                {user.uid === item.userId ? (
                  <TouchableOpacity
                    onPress={() => handleDelete(item.id)}
                    style={{marginLeft: '20%'}}>
                    <Icon
                      name="trash"
                      color={'#DC143C'}
                      size={30}
                      style={{marginTop: -10}}
                    />
                  </TouchableOpacity>
                ) : null}
              </View>
            </View>
            <View style={styles.book}>
              {item.bookImg !== null ? (
                <Image
                  style={styles.bookImage}
                  source={{
                    uri: item.bookImg,
                  }}></Image>
              ) : (
                <Text>SFSAFAS</Text>
              )}
            </View>
          </TouchableOpacity>
        )}></FlatList>
    </SafeAreaView>
  );
}

配置文件屏幕代码:

const onLogout = () => {
  firebase.auth().signOut();
};

export default function ProfileScreen({navigation, route}) {
  const [books, setBooks] = useState([]);
  const [loading, setLoading] = useState(true);
  const [query, setQuery] = useState('');
  const [userData, setUserData] = useState(null);
  const [deleted, setDeleted] = useState(false);
  const user = firebase.auth().currentUser;
  const userIdd = firebase.auth().currentUser.uid;

  const fetchBook = async () => {
    try {
      const list = [];

      await firestore()
        .collection('user_book')
        .where('userId', '==', route.params ? route.params.userId : user.uid)
        .orderBy('postTime', 'desc')
        .get()
        .then(querySnapshot => {
          // console.log('Total books from users', querySnapshot.size);
          querySnapshot.forEach(doc => {
            const {
              userId,
              title,
              bookImg,
              postTime,
              author,
              genre,
              summary,
              price,
            } = doc.data();
            list.push({
              id: doc.id,
              userId,
              title,
              bookImg,
              postTime: postTime,
              author,
              genre,
              summary,
              price,
            });
          });
        });

      setBooks(list);
      if (loading) {
        setLoading(false);
      }

      console.log('Books', list);
    } catch (e) {
      console.log(e);
    }
  };

  const getUser = async () => {
    await firestore()
      .collection('user')
      .doc(route.params ? route.params.userId : user.uid)
      .get()
      .then(documentSnapshot => {
        if (documentSnapshot.exists) {
          console.log('User Data', documentSnapshot.data());
          setUserData(documentSnapshot.data());
        }
      });
  };

  useEffect(() => {
    getUser();
    fetchBook();
  }, []);

  useEffect(() => {
    fetchBook();
    setDeleted(false);
  }, [deleted]);

  const deleteBook = bookId => {
    console.log(bookId);

    firestore()
      .collection('user_book')
      .doc(bookId)
      .get()
      .then(documentSnapshot => {
        if (documentSnapshot.exists) {
          const {bookImg} = documentSnapshot.data();

          if (bookImg !== null) {
            const sotageRef = storage().refFromURL(bookImg);
            const imageRef = storage().ref(sotageRef.fullPath);

            imageRef
              .delete()
              .then(() => {
                console.log('${bookImg} has been deleted');
                deleteFirestoreData(bookId);
                setDeleted(true);
              })
              .catch(e => {
                console.log('Error while deleting the image', e);
              });
          } else {
            deleteFirestoreData(bookId);
          }
        }
      });
  };

  const handleDelete = bookId => {
    Alert.alert('Delete Book', 'Are you sure?', [
      {
        text: 'Cancel',
        onPress: () => console.log('Cancel Pressed'),
        style: 'cancel',
      },
      {text: 'Confirm', onPress: () => deleteBook(bookId)},
    ]);
  };

  const deleteFirestoreData = bookId => {
    firestore()
      .collection('user_book')
      .doc(bookId)
      .delete()
      .then(() => {
        Alert.alert('Your book was deleted from MarketStore');
      })
      .catch(e => console.log('Error deleting book', e));
  };

  const ItemSeparatorView = () => {
    return (
      <View style={{height: 2, width: '100%', backgroundColor: 'black'}}></View>
    );
  };
  return (
    <SafeAreaView style={{flex: 1, backgroundColor: '#fff'}}>
      <ScrollView
        style={styles.container}
        contentContainerStyle={{justifyContent: 'center', alignItems: 'center'}}
        showsVerticalScrollIndicator={false}>
        <Image
          style={styles.userImg}
          source={{
            uri: userData
              ? userData.userImg
              : 'https://static.remove.bg/remove-bg-web/2a274ebbb5879d870a69caae33d94388a88e0e35/assets/start-0e837dcc57769db2306d8d659f53555feb500b3c5d456879b9c843d1872e7baa.jpg',
          }}></Image>

        <Text style={styles.userName}>{userData ? userData.name : 'Test'}</Text>
        <Text style={styles.userName}>
          {/* {route.params ? route.params.userId : userIdd} */}
        </Text>
        <Text style={styles.aboutUser}>
          {userData ? userData.about || 'No details added' : ''}
        </Text>

        <View style={styles.userBtnWrapper}>
          {route.params ? (
            <>
              <TouchableOpacity style={styles.userBtn} onPress={() => {}}>
                <Text style={styles.userBtnTxt}>Message</Text>
              </TouchableOpacity>
            </>
          ) : (
            <>
              <TouchableOpacity
                style={styles.userBtn}
                onPress={() => navigation.navigate('Messages')}>
                <Text style={styles.userBtnTxt}>My messages</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={styles.userBtn}
                onPress={() => {
                  navigation.navigate('EditProfile');
                }}>
                <Text style={styles.userBtnTxt}>Edit</Text>
              </TouchableOpacity>
              <TouchableOpacity
                style={styles.userBtn}
                onPress={() => onLogout()}>
                <Text style={styles.userBtnTxt}>Logout</Text>
              </TouchableOpacity>
            </>
          )}
        </View>

        <View style={styles.userInfoWrapper}>
          <View style={styles.userInfoItem}>
            <Text style={styles.userInfoTitle}></Text>
            <Text style={styles.userInfoSubTitle}>{books.length}Books</Text>
          </View>
        </View>

        {books.map(item => (
          <TouchableOpacity
            style={styles.bookFeed}
            onPress={() => navigation.navigate('UserBook', {item})}>
            <View style={styles.textViewStyle}>
              <Text style={styles.txt1}>
                Title:
                <Text style={styles.txt2}>{item.title}</Text>
              </Text>
              <Text style={styles.txt1}>
                Author:<Text style={styles.txt2}>{item.author}</Text>
              </Text>
              <Text style={styles.txt1}>
                Genre:<Text style={styles.txt2}>{item.genre}</Text>
              </Text>
              <Text style={styles.txt1}>
                Price:
                <Text style={{color: 'black', fontStyle: 'normal'}}>
                  {item.price} lei
                </Text>
              </Text>
              <View style={{flexDirection: 'row'}}>
                <Text>{moment(item.postTime.toDate()).fromNow()}</Text>
                {user.uid === item.userId ? (
                  <TouchableOpacity onPress={() => handleDelete(item.id)}>
                    <Icon
                      name="trash"
                      color={'#DC143C'}
                      size={30}
                      style={{marginLeft: '20%'}}
                    />
                  </TouchableOpacity>
                ) : null}
              </View>
            </View>
            <View style={styles.book}>
              {item.bookImg !== null ? (
                <Image
                  style={styles.bookImage}
                  source={{
                    uri: item.bookImg,
                  }}></Image>
              ) : (
                <Text>SFSAFAS</Text>
              )}
            </View>
          </TouchableOpacity>
        ))}
      </ScrollView>
    </SafeAreaView>
  );
}

【问题讨论】:

    标签: javascript firebase react-native google-cloud-firestore fetch


    【解决方案1】:

    最终,您的问题归结为两件事:您如何管理当前用户及其关联变量,以及您如何管理正在查看个人资料的用户的 ID。我将介绍每个问题的基础知识,但要让它们完全适应你的代码,这取决于你。

    登录用户状态

    当您的页面首次加载时,firebase.auth().currentUser 位于 an intermediate state 中,并将返回 null。此外,如果用户要注销,您的组件将不会更新以反映导致资源侦听器根据您的安全规则抛出错误的情况。要正确监听当前用户,您应该使用带有firebase.auth().onAuthStateChanged() 的效果挂钩。此侦听器报告用户何时登录、何时注销、何时验证其会话以及何时切换用户。

    这可以使用这些钩子来完成:

    const [user, setUser] = useState(undefined);
    // type of user: firebase.User | null | undefined
    //   - `undefined` means the user state is still being checked (e.g. show a loading icon)
    //   - `null` means not signed in
    //   - `firebase.User` means a user is signed in and has been validated
    
    // NOTE: onAuthStateChanged conveniently returns it's own cleanup function
    // NOTE: Make sure to pass in `[]`, otherwise `user` will get updated in an infinite loop
    useEffect(() => firebase.auth().onAuthStateChanged(setUser), []); 
    

    你也可以监听错误:

    const [user, setUser] = useState(undefined);
    // type of user: firebase.User | null | undefined
    //   - `undefined` means the user state is still being checked (e.g. show a loading icon)
    //   - `null` means not signed in
    //   - `firebase.User` means a user is signed in and has been validated
    
    useEffect(() => {
      return firebase.auth() // return listener's cleanup function
        .onAuthStateChanged({
          next: setUser,
          error(err) {
            // something unexpected went wrong with Firebase Auth
            console.error('onAuthStateChanged Error:', err);
            setUser(null); // treat as signed out for safety
          }
        });
    }, []); // make sure to use `[]` here, otherwise `user` will be updated in an infinite loop
    

    获取要查看的用户ID

    现在您可以保证user 正常工作,您可以获取要查看的配置文件的用户ID。根据您现有的代码,这可以作为参数给出,也可以使用当前用户 ID。但是,我们需要能够处理以下情况:

    ID param User type: viewedProfileId Action
    provided signed in string access given profile's data
    provided signed out string access given profile's data (if public) OR
    navigate to login page (if auth required)
    provided initializing string access given profile's data (if public) OR
    show loading icon (if auth required)
    omitted signed in string access own user's profile data
    omitted signed out null navigate to login page
    omitted initializing undefined show loading icon

    如上表中创建viewedProfileId,您可以使用:

    const { userId: userIdParam } = route.params || {};
    // type of userIdParam: string | undefined
    
    const currentUserId = user ? user.uid : undefined;
    // type of currentUserId: string | undefined
    // for convenience over using `user && user.uid` repeatedly
    
    const viewedProfileId = userIdParam || currentUserId || user;
    // intended type of viewedProfileId: string | null | undefined
    // the value of `viewedProfileId` will be:
    //   `userIdParam` (if truthy), OR
    //   the current user's ID (if signed in and valid), OR
    //   `null` (when user is signed out), OR
    //   `undefined` (when authentication is still initializing)
    
    // TODO: Make use of viewedProfileId in hooks/render
    

    注意: 虽然上面的 sn-p 可以适应使用useStateuseEffect,但它并没有直接使用任何异步函数,这意味着使用状态方式只会导致不必要的组件重新渲染。

    脚手架

    将以上部分组合在一起,产生以下起点。正如我之前所说,完全重构代码超出了 StackOverflow 的范围。这些 cmets 仅供参考,一旦您看到发生了什么,大多数都可以删除。此代码还假设用户必须先登录才能获取个人资料信息。

    const [user, setUser] = useState(undefined);
    // type of user: firebase.User (signed in) | null (signed out) | undefined (initializing)
    
    // Attach listener for user state changes
    useEffect(() => firebase.auth().onAuthStateChanged(setUser), []); 
    
    const { userId: userIdParam } = route.params || {};
    // type of userIdParam: string (view profile of given ID) | undefined
    
    const currentUserId = user ? user.uid : undefined;
    // type of currentUserId: string (when signed in) | undefined (when signed out/initializing)
    // for convenience over using `user && user.uid` repeatedly
    
    const viewedProfileId = userIdParam || currentUserId || user;
    // intended type of viewedProfileId: string | null | undefined
    // the value of `viewedProfileId` will be:
    //   the `userId` route parameter (if truthy - view profile of this ID), OR
    //   the current user's ID (if signed in and valid - view own profile), OR
    //   `null` (when user is signed out - error state), OR
    //   `undefined` (when authentication is still initializing - still loading)
    
    const [viewedProfileData, setViewedProfileData] = useState(undefined);
    // NOTE: named `viewedProfileData` because conventionally `userData` should be reserved for the current user's data (not potentially another user)
    // type of viewedProfileData: ProfileDataModel | null | undefined
    //   - `undefined` means `viewedProfileId` hasn't been set yet and/or data
    //      hasn't been loaded yet (e.g. show a "loading profile" loading icon)
    //   - `null` means the profile data doesn't exist
    //   - `ProfileDataModel` means a profile has been loaded successfully
    
    const [viewedProfileBookList, setViewedProfileBookList] = useState(undefined);
    // NOTE: named `viewedProfileBookList` because `books` should be `bookList` (prevent typos against `book`) and then named to match `viewedProfileData`
    // type of viewedProfileData: BookModel[] | undefined
    //   - `undefined` means `viewedProfileId` hasn't been set yet and/or data
    //      hasn't been loaded yet (e.g. show a "searching for books" loading icon)
    //   - `BookModel[]` means a profile's book list has been loaded successfully (may be empty)
    
    // Fetch profile data, after suitable user state
    useEffect(() => {
      // While loading user state, don't fetch any data
      if (typeof user === "undefined") {
        return; 
      }
    
      // When not logged in, navigate away to login screen
      if (user === null) {
        navigation.navigate('Login');
        return;
      }
    
      // If here, user is logged in and viewedProfileId will be a user ID to grab (that may not exist!)
    
      fetchProfile(viewedProfileId);  // updates viewedProfileData
      fetchBookList(viewedProfileId); // updates viewedProfileBookList
    
      return () => {
        setViewedProfileData(undefined);
        setViewedProfileBookList(undefined);
      }
    }, [viewedProfileId, user]);
    
    // convert the state variables for use in the render
    const loading = typeof viewedProfileData === "undefined";
    const loadingBookList = typeof viewedProfileBookList === "undefined";
    const isOwnProfile = viewedProfileId === currentUserId;
    
    // e.g. use `currentUserId === item.userId` instead of `user.uid === item.userId`
    // e.g. use `isOwnProfile` instead of `route.params`
    
    return (/* the render */);
    

    注意:在上面的代码中,我使用了三值逻辑:undefined(初始化)、null(无效/未找到)和“其他”(值)。是否要执行相同操作或使用双变量方法取决于您:someVariable(该值,具有一些默认值)和someVariableLoaded(布尔值,说明someVariable 是否有效或不是)。使用后者将适合像书籍列表这样的数组结构。

    【讨论】:

      猜你喜欢
      • 2020-12-04
      • 1970-01-01
      • 2015-02-09
      • 1970-01-01
      • 1970-01-01
      • 2013-01-19
      • 2021-04-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多