响应对象的所有属性都不为空,但某些对象属性显示为空
All attributes of response object is not null but some object attriubes shown as null
我有一个组件显示从 Firestore 获取的 table 中的数据,即使获取的数据是完整的,但在 JSX 上访问时 setting
属性大部分时间为 null。我有时会随机看到一瞥正确的输出。
据我所知,第一次渲染 histories
将为空,但在第二次渲染时不会。但似乎即使在第二个渲染中 settings
也是空的。我在这里遗漏了什么?
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> {item[1].questionnaireType}</span>
</div>
<div>{convertTimestampToDate(item[1].timestamp.seconds)}</div>
</td>
<td>
<div>
<span>{item[2].score}</span>
<span> {item[2].questionnaireType}</span>
</div>
<div>
{convertTimestampToDate(item[2].timestamp.seconds)}
</div>
</td>
<td>
<div>
<span>Sms:</span>
<span> {displaySettings(item[0].settings, "sms")}</span>
</div>
<div>
<span>Call:</span>
<span> {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");
}
页面加载时的控制台输出
[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
响应对象:控制台输出[Array(3)]
[
[
{
"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;
}
}
更新:添加了readHistory
功能
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");
}
问题出在以下代码中,我正在为 groupedHistories
对象中的每个 key
发出 async
请求并设置 History
对象 settings
属性 无需等待所有 async
请求完成。
Object.keys(groupedHistories).forEach(function (key) {
readUser(key).then((user) => {
if (user) {
groupedHistories[key].forEach(function (history: History) {
history.settings = user.settings;
});
}
});
});
解决方案是使用 Promise.all()
等待所有 promises
获得 resolved
并将 returned values
分配给 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;
我有一个组件显示从 Firestore 获取的 table 中的数据,即使获取的数据是完整的,但在 JSX 上访问时 setting
属性大部分时间为 null。我有时会随机看到一瞥正确的输出。
据我所知,第一次渲染 histories
将为空,但在第二次渲染时不会。但似乎即使在第二个渲染中 settings
也是空的。我在这里遗漏了什么?
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> {item[1].questionnaireType}</span>
</div>
<div>{convertTimestampToDate(item[1].timestamp.seconds)}</div>
</td>
<td>
<div>
<span>{item[2].score}</span>
<span> {item[2].questionnaireType}</span>
</div>
<div>
{convertTimestampToDate(item[2].timestamp.seconds)}
</div>
</td>
<td>
<div>
<span>Sms:</span>
<span> {displaySettings(item[0].settings, "sms")}</span>
</div>
<div>
<span>Call:</span>
<span> {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");
}
页面加载时的控制台输出
[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
响应对象:控制台输出[Array(3)]
[
[
{
"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;
}
}
更新:添加了readHistory
功能
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");
}
问题出在以下代码中,我正在为 groupedHistories
对象中的每个 key
发出 async
请求并设置 History
对象 settings
属性 无需等待所有 async
请求完成。
Object.keys(groupedHistories).forEach(function (key) {
readUser(key).then((user) => {
if (user) {
groupedHistories[key].forEach(function (history: History) {
history.settings = user.settings;
});
}
});
});
解决方案是使用 Promise.all()
等待所有 promises
获得 resolved
并将 returned values
分配给 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;