skip to Main Content

I am using an array and useState() to fetch the data from the firebase database. The array is initially empty. However after I recieve the data from the firebase after its initial loading and try to forward the data to other component usig props for rendering it, the initial empty array is forwarded. I used useEffect() with the loading (from useAuthState()) to fetch the data once the firebase is done loading.

Here’s my code
User Component

import { useEffect, useState } from "react";
import { auth, db } from "../firebaseConfig";
import { useNavigate } from "react-router-dom";
import { useAuthState } from "react-firebase-hooks/auth";
import { CircularProgress } from "@mui/material";
import UserDataTable from "../Components/User-data-table/UserDataTable";

function User() {
  const [userData, setUserData] = useState([]);

  const [user, loading] = useAuthState(auth);
  const navigate = useNavigate();

  const fetchUserData = () => {
    const resultsReference = db.collection("Results");
    const { uid } = auth.currentUser;

    let tempData = [];

    resultsReference
      .where("userId", "==", uid)
      .get()
      .then((snapshot) => {
        snapshot.docs.forEach((doc) => {
          tempData.push({ ...doc.data() });
        });
        setUserData(tempData);
      });
  };

  const effectFunction = async () => {
    if (loading) {
      return <CircularProgress />;
    }
  };

  useEffect(() => {
    if (!loading) {
      fetchUserData();
    }

    if (!loading && !user) {
      navigate("/");
    }

    effectFunction();
  }, [loading]);

  return (
    <div>
      <UserDataTable userData={userData} />
    </div>
  );
}

export default User;

UserDataTable Component

import {
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
} from "@mui/material";
import "./UserDataTable.scss";
import { useMyTheme } from "../../Context/ThemeContext";

function UserDataTable({ userData }) {
  const { theme } = useMyTheme();

  const tableStyles = {
    color: theme.textColor,
    fontFamily: ["Kode Mono", "monospace"].join(","),
    textAlign: "center",
    fontSize: "2rem",
  };

  return (
    <div className="user-table">
      <TableContainer>
        <Table>
          <TableHead>
            <TableRow>
              <TableCell style={tableStyles}>WPM</TableCell>
              <TableCell style={tableStyles}>Raw WPM</TableCell>
              <TableCell style={tableStyles}>Accuracy</TableCell>
              <TableCell style={tableStyles}>Characters</TableCell>
              <TableCell style={tableStyles}>Date</TableCell>
            </TableRow>
          </TableHead>
          <TableBody>
            {userData.map((d) => { **// Since the prop is empty i am unable to render it here !**
              <TableRow>
                <TableCell>{d.wpm}</TableCell>
                <TableCell>{d.rawWpm}</TableCell>
                <TableCell>{d.accuracy}</TableCell>
                <TableCell>0|0|0|0</TableCell>
                <TableCell>{d.timeStamp.toDate().toLocaleString()}</TableCell>
              </TableRow>;
            })}
          </TableBody>
        </Table>
      </TableContainer>
    </div>
  );
}

export default UserDataTable;

I think this might be a problem when using useEffect() as when the component reloads, the empty array is being forwarded to the next component. I am new to react and was following a tutorial however the instructor did not face this issue. Please Help

I also think that the data is being forwarded to the next component. But the component processes the empty array first and then it never processes the non empty array. How do I fix this.

When consoling the data, first empty array, then filled arrays are observed:

2

Answers


  1. Since you initialize the state to an empty array here const [userData, setUserData] = useState([]);, that’ll be the initial state with which your UserDataTable is rendered.

    Your UserDataTable code will either need to handle that initial state, or your User will need to only render the UserDataTable component when the data has been loaded.

    Login or Signup to reply.
  2. Your state update code and passing as props is fine, but you have a couple logical issues.

    • The useEffect hook can’ call effectFunction and render the loading UI, CircularProgress must be rendered as part of the render return of the User component.

      function User() {
        const [userData, setUserData] = useState([]);
        const [user, loading] = useAuthState(auth);
        const navigate = useNavigate();
      
        const fetchUserData = () => {
          const resultsReference = db.collection("Results");
          const { uid } = auth.currentUser;
      
          const tempData = [];
      
          resultsReference
            .where("userId", "==", uid)
            .get()
            .then((snapshot) => {
              snapshot.docs.forEach((doc) => {
                tempData.push({ ...doc.data() });
              });
              setUserData(tempData);
            });
        };
      
        useEffect(() => {
          if (!loading) {
            fetchUserData();
          }
      
          if (!loading && !user) {
            navigate("/");
          }
        }, [loading]);
      
        if (loading) {
          return <CircularProgress />;
        }
      
        return (
          <div>
            <UserDataTable userData={userData} />
          </div>
        );
      }
      
    • The UserDataTable must return a value when mapping the row data. The arrow function body is missing a return statement returning the row JSX. Your table UI isn’t working because no rows are rendered.

      Use an explicit return from the function body:

      <TableBody>
        {userData.map((d) => {
          return (
            <TableRow>
              <TableCell>{d.wpm}</TableCell>
              <TableCell>{d.rawWpm}</TableCell>
              <TableCell>{d.accuracy}</TableCell>
              <TableCell>0|0|0|0</TableCell>
              <TableCell>{d.timeStamp.toDate().toLocaleString()}</TableCell>
            </TableRow>
          );
        })}
      </TableBody>
      

      Use an implicit return without a function body, e.g. direct return:

      <TableBody>
        {userData.map((d) => (
          <TableRow>
            <TableCell>{d.wpm}</TableCell>
            <TableCell>{d.rawWpm}</TableCell>
            <TableCell>{d.accuracy}</TableCell>
            <TableCell>0|0|0|0</TableCell>
            <TableCell>{d.timeStamp.toDate().toLocaleString()}</TableCell>
          </TableRow>;
        ))}
      </TableBody>
      

    The initially empty userData array is passed to UserDataTable, and when the state updates, the updated userData state is passed as a prop and UserDataTable will rerender.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search