Home > Software engineering >  All attributes of response object is not null but some object attriubes shown as null
All attributes of response object is not null but some object attriubes shown as null

Time:03-23

I have component which display data in a table fetched from Firestore, even though the fetched data is complete, but setting attribute is most of the time is null when accessing on JSX. I sometimes see glimpse correct output like randomly.

As I know first render histories going to be null but not on the second render. But it seems even in the second render settings is null. I am missing something here?

DataTable.tsx

export default function DataTable() {
  const [histories, setHistories] = useState<Array<Array<History>>>([]);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    readHistories().then((item) => {
      if (item) {
        console.log(item);
        setHistories(item);
        setLoading(false);
      }
    });
  }, [setHistories, setLoading]);

  return (
    <Table striped highlightOnHover verticalSpacing="md">
      <thead>
        <tr>
          <th>UID</th>
          <th>Test 1</th>
          <th>Test 1</th>
          <th>Test 3</th>
          <th>Settings</th>
        </tr>
      </thead>
      <tbody>
        {!loading ? (
          histories != null ? (
            histories.map((item: Array<History>, index: number) => {
              return (
                <Link key={index} href={`profile/${item[0].uid}`} passHref>
                  <tr>
                    <td>
                      {trimUid(item[0].uid)}
                    </td>
                    <td>
                      <div>
                        <span>{item[0].score} </span>
                        <span>{item[0].questionnaireType}</span>
                      </div>
                      <div>{convertTimestampToDate(item[0].timestamp.seconds)}</div>
                    </td>
                    <td>
                      <div>
                        <span>{item[1].score}</span>
                        <span>&nbsp;{item[1].questionnaireType}</span>
                      </div>
                      <div>{convertTimestampToDate(item[1].timestamp.seconds)}</div>
                    </td>
                    <td>
                      <div>
                        <span>{item[2].score}</span>
                        <span>&nbsp;{item[2].questionnaireType}</span>
                      </div>
                      <div>
                        {convertTimestampToDate(item[2].timestamp.seconds)}
                      </div>
                    </td>
                    <td>
                      <div>
                        <span>Sms:</span>
                        <span>&nbsp;{displaySettings(item[0].settings, "sms")}</span>
                      </div>
                      <div>
                        <span>Call:</span>
                        <span>&nbsp;{displaySettings(item[0].settings, "call")}</span>
                      </div>
                    </td>
                  </tr>
                </Link>
              );
            })
          ) : (
            ""
          )
        ) : (
          <tr>
            <td>Loading...</td>
          </tr>
        )}
      </tbody>
    </Table>
  );
}

...

function displaySettings(settings: any, key: string) {
  if (settings != null && key == "sms") {
    let onOff = settings.smsOn ? "On" : "Off";
    console.log("Sms On : "   onOff);
    return onOff;
  } else console.log("Settings Sms is null");

  if (settings != null && key == "call") {
    let onOff = settings.callOn ? "On" : "Off";
    console.log("Call On : "   onOff);
    return onOff;
  } else console.log("Settings Call is null");
}

Console Output on page load

[Array(3)]
DataTable.tsx?a927:135 Settings Sms is null
DataTable.tsx?a927:141 Settings Call is null
DataTable.tsx?a927:135 Settings Sms is null
DataTable.tsx?a927:141 Settings Call is null

Response Object : [Array(3)] in console output

[
    [
        {
            "uid": "prW2afyS3WVMaUsnKoXBoEyrvLR2",
            "timestamp": {
                "seconds": 1647168151,
                "nanoseconds": 124000000
            },
            "questionnaireType": "EPDS",
            "score": 16,
            "settings": {
                "callOn": true,
                "smsOn": true
            }
        },
        {
            "uid": "prW2afyS3WVMaUsnKoXBoEyrvLR2",
            "timestamp": {
                "seconds": 1647163878,
                "nanoseconds": 998000000
            },
            "questionnaireType": "PHQ9",
            "score": 18,
            "settings": {
                "callOn": true,
                "smsOn": true
            }
        },
        {
            "uid": "prW2afyS3WVMaUsnKoXBoEyrvLR2",
            "timestamp": {
                "seconds": 1647162553,
                "nanoseconds": 5000000
            },
            "questionnaireType": "PHQ9",
            "score": 17,
            "settings": {
                "callOn": true,
                "smsOn": true
            }
        }
    ]
]

History.ts

import { Timestamp } from "firebase/firestore";
import { QuestionnaireType } from "./QuestionnaireType";
import { Settings } from "./Settings";
export class History {
  uid: string;
  timestamp: Timestamp;
  questionnaireType: QuestionnaireType; // enum
  score: number;
  settings: Settings;

  constructor(
    uid: string,
    timestamp: Timestamp,
    questionnaireType: QuestionnaireType,
    score: number,
    settings: Settings
  ) {
    this.uid = uid;
    this.timestamp = timestamp;
    this.questionnaireType = questionnaireType;
    this.score = score;
    this.settings = settings;
  }
}

Settings.ts

export class Settings {
  smsOn: boolean;
  callOn: boolean;

  constructor(smsOn: boolean, callOn: boolean) {
    this.smsOn = smsOn;
    this.callOn = callOn;
  }
}

Update : Added readHistory function

export async function readHistories() {
  const q = query(
    collectionGroup(db, "History"),
    where("score", ">=", 10)
  ).withConverter(historyConverter);
  const querySnapshot = await getDocs(q);
  const histories = Array<History>();
  querySnapshot.forEach((doc) => {
    if (doc.exists()) {
      const history: History = doc.data();
      history.uid = doc.ref.parent.parent!.id;
      histories.push(history);
    } else console.log("No documents!");
  });
  return filterLastMonthHistories(histories);
}

function filterLastMonthHistories(histories: Array<History>) {
  // ...

  const groupedHistories = groupByUid(recentHistories, "uid");

  Object.keys(groupedHistories).forEach(function (key) {
    readUser(key).then((user) => {
      if (user) {
        groupedHistories[key].forEach(function (history: History) {
          history.settings = user.settings;
        });
      }
    });
  });

  return sortByTimestampLimit(groupedHistories);
}



function groupByUid(histories: Array<History>, key: string) {
  // ...
}

function sortByTimestampLimit(arr: Object) {
  // ...
  return sortedRecentHistories;
}

export async function readUser(docId: string) {
  const docRef = doc(db, "Users", docId).withConverter(userConverter);
  const docSnap = await getDoc(docRef);

  if (docSnap.exists()) {
    let user = docSnap.data();
    return user;
  } else console.log("No such document");
}

CodePudding user response:

Problem was in following piece of code I was making async request for every key in groupedHistories object and setting the History object settings property without waiting for all the async requests to complete.

 Object.keys(groupedHistories).forEach(function (key) {
    readUser(key).then((user) => {
      if (user) {
        groupedHistories[key].forEach(function (history: History) {
          history.settings = user.settings;
        });
      }
    });
  });

Solution was to use Promise.all() which wait until all promises get resolved and assign returned values to an array.

async function filterLastMonthHistories(histories: Array<History>) {

  // ...

  const groupedHistories = groupByUid(recentHistories, "uid");
  const uIds = Object.keys(groupedHistories);

  const sortedRecentHistories = sortByTimestampLimit(groupedHistories);

  const users: Array<User | undefined> = await Promise.all(
    uIds.map(function (uid) {
      return readUser(uid);
    })
  );

  sortedRecentHistories.forEach(function (histories) {
    histories.forEach(function (history) {
      users.forEach(function (user) {
        if (user != undefined) {
          history.settings = user.settings;
        }
      });
    });
  });

  return sortedRecentHistories;
  • Related