Array.map() 在 React 中不渲染任何东西

IT技术 javascript arrays reactjs firebase google-cloud-firestore
2022-07-25 01:46:07

我正在尝试在我的react应用程序中列出一个列表。我已经从我的数据库中检索到数据,并将其推送到一个列表中。我已经仔细检查了数据是否在控制台中正确显示,并且确实如此,但是 array.map() 什么也不返回。我认为问题可能是 array.map() 运行了两次。我不知道为什么它会运行两次。

function Dashboard() {
    const user = firebase.auth().currentUser;
    const [teams, setTeams] = useState([])
    const history = useHistory();

    useEffect(() => {
        getTeams()

        if (user) {

        } else {
            history.push("/")
        }
    }, [])

    function Welcome() {
        if (user) {
            return <h1>Welcome, {user.displayName}</h1>
        } else {

        }
    }

    const getTeams = () => {
        firebase.firestore().collectionGroup('members').where('user', '==', user.uid).get().then((snapshot) => {
            const docList = []
            snapshot.forEach((doc) => {
                    docList.push({
                        teamId: doc.data().teamId,
                    })
            })
            const teamslist = []
            docList.forEach((data) => {
                firebase.firestore().collection('teams').doc(data.teamId).get().then((doc) => {
                    teamslist.push({
                        name: doc.data().name,
                        teamId: doc.id,
                    })
                })
            })
            setTeams(teamslist)
        })
    }

    const openTeam = (data) => {
        console.log(data.teamId)
    }

    return (
        <div>
            <Welcome />
            <div>
                <ul>
                    {console.log(teams)}
                    {teams.map((data) => {
                        return (
                            <li onClick={() => openTeam(data)} key={data.teamId}>
                                <h1>{data.name}</h1>
                                <p>{data.teamId}</p>
                            </li>
                        )
                        })}
                </ul>
            </div>
        </div>
    )
}

export default Dashboard

团队数组

2个回答

getTeams函数有一个错误,它在调用之前没有等待firebase.firestore().collection('teams').doc(data.teamId).get().thenpromise 完成setTeams,因此使用空数组调用它,导致 React 使用空数组触发渲染。

随着获取每个团队的Promise解决,它们将被推送到相同的数组引用,但这不会触发 React 中的重新渲染,因为setTeams当数组更改时您不会再次调用。

setTeams试试这个代码,它在生成的每个团队PromisedocList被解决之前不会调用。

const getTeams = () => {
    firebase.firestore().collectionGroup('members').where('user', '==', user.uid).get().then((snapshot) => {
        const docList = []

        snapshot.forEach((doc) => {
            docList.push({
                teamId: doc.data().teamId,
            })
        })

        const teamslist = [];

        Promise.all(docList.map((data) => {
            return firebase
                .firestore()
                .collection('teams')
                .doc(data.teamId)
                .get()
                .then((doc) => {
                    teamslist.push({
                        name: doc.data().name,
                        teamId: doc.id,
                    })
                })
        }))
            .then(() => setTeams(teamslist));
    })
}

一个较小的编辑是setTeams在每个单独的团队Promise解决后调用,这将在每次解决新团队时触发 React 渲染:

.then((doc) => {
    teamslist.push({
        name: doc.data().name,
        teamId: doc.id,
    });
    // create a new array, since using the same array
    // reference won't cause react to rerender
    setTeams([...teamslist]);
})

非常感谢@martinstark在我不在时为您提供了答案

但是,还有一些需要涵盖的内容。

用户状态

在您当前的组件中,您从 Firebase 身份验证中提取当前用户,但不处理该用户的状态更改 - 登录、注销、切换用户。如果用户已登录并且他们将直接导航到您的仪表板,则firebase.auth().currentUser可能会null在解决用户的登录状态时暂时将其发送到您的登录页面。

这可以使用以下方法添加:

const [user, setUser] = useState(() => firebase.auth().currentUser || undefined);
const userLoading = user === undefined;

useEffect(() => firebase.auth().onAuthStateChanged(setUser), []);

接下来,在您的第一次useEffect通话中,您会调用getTeams()用户是否已登录 - 但它应该取决于当前用户。

useEffect(() => {
  if (userLoading) {
    return; // do nothing (yet)
  } else if (user === null) {
    history.push("/");
    return;
  }
  
  getTeams()
    .catch(setError);
}, [user]);
// This getTeams() is a () => Promise<void>
const getTeams = async () => {
  const membersQuerySnapshot = await firebase.firestore()
    .collectionGroup('members')
    .where('user', '==', user.uid)
    .get();
  
  const docList = []
  membersQuerySnapshot.forEach((doc) => {
    docList.push({
      teamId: doc.get("teamId"), // better perfomance than `doc.data().teamId`
    });
  });

  const teamDataList = [];

  await Promise.all(docList.map((data) => {
    return firebase.firestore()
      .collection('teams')
      .doc(data.teamId)
      .get()
      .then(doc => teamDataList.push({
        name: doc.get("name"),
        teamId: doc.id
      }));
  }));

  setTeams(teamDataList);
}

优化getTeams()- 网络调用

getTeams您的问题中的函数setTeams使用数组调用,如@martinstark 的回答[]中所述,该函数在调用它时将为空“获取团队数据”操作是异步的,您不会在更新状态和触发新渲染之前等待它们解决。在组件渲染后向它们推送数据时,修改数组不会触发新的渲染。

虽然您可以使用 获取每个团队的数据,但db.collection("teams").doc(teamId).get()每个请求都是网络调用,您只能并行进行有限数量的请求。因此,您可以每次网络调用最多获取 10 个团队,而不是使用in运算符FieldPath.documentId().

假设collectionGroup("members")目标文档集合/teams/{aTeamId}/members包含(至少):

"/teams/{aTeamId}/members/{memberUserId}": {
  teamId: aTeamId,
  user: memberUserId, // if storing an ID here, call it "uid" or "userId" instead
  /* ... */
}
// this utility function lives outside of your component near the top/bottom of the file
function chunkArr(arr, n) {
  if (n <= 0) throw new Error("n must be greater than 0");
  return Array
    .from({length: Math.ceil(arr.length/n)})
    .map((_, i) => arr.slice(n*i, n*(i+1)))
}

// This getTeams() is a () => Promise<void>
const getTeams = async () => {
  const membersQuerySnapshot = await firebase.firestore()
    .collectionGroup('members')
    .where('user', '==', user.uid)
    .get();

  const teamIDList = []
  membersQuerySnapshot.forEach((doc) => {
    teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
  })

  const chunkedTeamIDList = chunkArr(teamIDList, 10) // split into batches of 10
  const teamsColRef = firebase.firestore().collection('teams');
  const documentId = firebase.firestore.FieldPath.documentId(); // used with where() to target the document's ID
  const foundTeamDocList = await Promise
    .all(chunkedTeamIDList.map((chunkOfTeamIDs) => {
      // fetch each batch of IDs
      return teamsColRef
        .where(documentId, 'in', chunkOfTeamIDs)
        .get();
    }))
    .then((arrayOfQuerySnapshots) => {
      // flatten results into a single array
      const allDocsList = [];
      arrayOfQuerySnapshots.forEach(qs => allDocsList.push(...qs.docs));
      return allDocsList;
    });

  const teamDataList = foundTeamDocList
    .map((doc) => ({ name: doc.get("name"), teamId: doc.id }));

  // sort by name, then by ID
  teamDataList.sort((aTeam, bTeam) =>
    aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
  )

  // update state & trigger render
  setTeams(teamDataList);
}

您还可以使用此实用功能来简化和优化代码。这使:

// This getTeams() is a () => Promise<void>
const getTeams = async () => {
  const membersQuerySnapshot = await firebase.firestore()
    .collectionGroup('members')
    .where('user', '==', user.uid)
    .get();

  const teamIDList = []
  membersQuerySnapshot.forEach((doc) => {
    teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
  })
  
  const teamsColRef = firebase.firestore().collection('teams');
  const teamDataList = [];
  await fetchDocumentsWithId(
    teamsColRef,
    teamIDList,
    (doc) => teamDataList.push({ name: doc.get("name"), teamId: doc.id })
  );

  // sort by name, then by ID
  teamDataList.sort((aTeam, bTeam) =>
    aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
  )

  // update state & trigger render
  setTeams(teamDataList);
}

优化getTeams()- 函数定义

作为最后优化的一部分,您可以将其从组件中拉出或将其放在自己的文件中,这样就不会在每次渲染时重新定义它:

// define at top/bottom of the file outside your component
// This getTeams() is a (userId: string) => Promise<{ name: string, teamId: string}[]>
async function getTeams(userId) => {
  const membersQuerySnapshot = await firebase.firestore()
    .collectionGroup('members')
    .where('user', '==', userId)
    .get();

  const teamIDList = []
  membersQuerySnapshot.forEach((doc) => {
    teamIDList.push(doc.get("teamId")); // better perfomance than `doc.data().teamId`
  })

  const teamsColRef = firebase.firestore().collection('teams');
  const teamDataList = [];
  await fetchDocumentsWithId(
    teamsColRef,
    teamIDList,
    (doc) => teamDataList.push({ name: doc.get("name"), teamId: doc.id })
  );

  // sort by name, then by ID
  teamDataList.sort((aTeam, bTeam) =>
    aTeam.name.localeCompare(bTeam.name) || aTeam.teamId.localeCompare(bTeam.teamId)
  )

  // return the sorted teams
  return teamDataList
}

并更新您的使用方式:

useEffect(() => {
  if (userLoading) {
    return; // do nothing
  } else if (user === null) {
    history.push("/");
    return;
  }
  
  getTeams(user.uid)
    .then(setTeams)
    .catch(setError);
}, [user]);