我的状态在第一次渲染中是空数组,登录后是空白页



在我在Android上运行的React Ionic项目中,我希望能够登录并检查我作为用户是否同意我们的使用条款。为此,我编写了以下函数,其中包括:

  • '/login'API 端点发出的 POST 请求(第一个请求)
  • '/terms-of-use'终结点的 GET 请求
  • GET请求到'/accepted-terms-of-use'端点,使用标头中第一个请求response.data.token的令牌
  • '/terms-of-use''/accepted-terms-of-use'进行比较,并在"TermsOfUse.tsx"中呈现用户尚未通过数组接受的内容missingTermsofUse

如果数组missingTermsofUse长度0,则表示用户已经接受了所有使用条款,我们将他推送到homePage,否则我们将他推送到TermsOfUse页面。

我正在使用上下文,以便在应用程序中的任何地方使用道具

登录.tsx

const history = useHistory();
const {
termsofUse, 
setTermsofUse,
acceptedTermsofUse, 
setAcceptedTermsofUse,
missingTermsofUse, 
setMissingTermsofUse
} = React.useContext(MyContext);
const doLogin = async (e: any) => {
e.preventDefault();

const loginData = {
email: email,
password: password,
};

// plugin: https://github.com/capacitor-community/http
const options = {
url: "https://xxx/login",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
data: loginData,
};


await Http.request({
...options,
method: "POST",
})
.then((response: any) => {
if (response.status == 201) {
if (response.data.token) {
localStorage.setItem("user",   JSON.stringify(response.data));
const options1 = {
url: "https://xxx/terms-of-use",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
};
const options2 = {
url: "https://xxx/accepted-terms-of-use",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Token: response.data.token,
},
};
Http.request({ ...options1, method: "GET" }).then(
(response1: any) => {
setTermsofUse(response1.data);

Http.request({ ...options2, method: "GET" }).then(
(response2: any) => {
setAcceptedTermsofUse(response2.data);
//find terms of use which user has not accepted (entries which are present in response1 but not present in response2)
setMissingTermsofUse(
termsofUse.filter(
({
id,
name,
}: {
id: number;
name: number;
}) =>
!acceptedTermsofUse.find(
(entry: any) =>
entry.id == id && entry.name == name
)
)
);
}
);
}
);

}

missingTermsofUse.length != 0 ? history.push("/termsOfUse") : history.push("/homePage");
}
.catch((error) => {
console.log("Error:" + JSON.stringify(error));
});
};

登录后,我得到一个空白页面,既不是TermsofUse页面,也不是homePage。 您知道可能导致错误的原因吗?我认为第一个渲染包含null值,但我真的不知道如何重新编写请求以避免这种情况(如果这导致了问题)。

我是 React 的新手,任何帮助将不胜感激。

编辑:

从日志中,我可以看到termsofUseacceptedTermsofUsemissingTermsofUse是空的。这意味着在登录期间,它们在 Context.tsx 中设置的初始值将被呈现。 我该如何解决这个问题?

我对 React 不是很了解,但我发现了几个与如何处理异步代码相关的潜在问题:

  1. 您同时使用 await 和 .then()
await Http.request({
...options,
method: "POST",
})
.then((response: any) => {
...

我建议只坚持一种处理承诺的方法。你可以在谷歌上找到很多关于async/await与then/catch的信息,这是我认为很好解释的结果之一(不是我的): https://medium.com/@dio.hamidou/async-await-vs-then-catch-4f64d42e6392

acceptedTermsofUse.length != 0 ? history.push("/termsOfUse") : history.push("/homePage");

将在您从 GET '/使用条款' 和 '/accept-use-of-use' 获得答案之前执行,因为它不在最内部的 .then() 中。这可能是您缺少空使用条款的原因

希望这有助于进一步调试您的问题!

在你的承诺链深处,你打电话给setMissingTermsofUse。如果我理解正确,该调用似乎假定termsofUse的值已更新(由于前面对setTermsofUse的调用)。但是,在 React 中,对设置状态的调用是异步的,因此您的代码不能依赖于它们立即发生。我的猜测是,你的doLogin函数正在关闭一个过时的值termsOfUse(可能是你初始化它的空数组) - 然后过滤那个空数组,这自然只会给你一个空数组。

我认为您要么需要在效果中移动负责找出"缺少使用条款"的逻辑(使用useEffectReact 钩子),然后仅在更新termsofUseacceptedTermsofUse时才激活效果。效果中的代码将可以访问更新的值(因为 React 在状态发生变化后运行效果)。

或者,您已经可以访问doLogin函数中较新的termsofUseacceptedTermsofUse值(从您发送的请求中)。因此,也许只是直接使用这些响应的数据来找出缺少的使用条款。

未经测试,但类似:

const doLogin = async (e: any) => {
e.preventDefault();
const loginData = {
dateOfBirth: birthday,
postalCode: postalCode,
};
// plugin: https://github.com/capacitor-community/http
const loginResponse = await Http.request({
url: "https://xxx/login",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
data: loginData,
method: "POST",
});
if (201 !== loginResponse.status || !loginResponse.data.token) {
return console.log(
"Response status indicates resource may not have been created or no token was included in response.",
{ response: loginResponse }
);
}
localStorage.setItem("user", JSON.stringify(loginResponse.data));
const token = loginResponse.data.token;
const termsOfUseResponse = await Http.request({
url: "https://xxx/terms-of-use",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
method: "GET",
});
setTermsofUse(termsOfUseResponse.data);
const acceptedTermsOfUseResponse = await Http.request({
url: "https://xxx/accepted-terms-of-use",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Token: token,
},
});
setAcceptedTermsofUse(acceptedTermsOfUseResponse.data);
const missingTermsOfUse = termsOfUseResponse.data.filter(
({ id, name }: { id: number; name: number }) =>
!acceptedTermsOfUseResponse.data.some(
(entry: any) => entry.id == id && entry.name == name
)
);
setMissingTermsofUse(missingTermsOfUse);
missingTermsOfUse.length !== 0
? history.push("/termsOfUse")
: history.push("/homePage");
};

所做的主要更改是:

  • 扁平化了您的嵌套承诺链和异步逻辑,所以希望它更具可读性,尽管
    • 它缺少一些错误处理
    • 如果您在一些异步操作(即此处的 HTTP 请求)后设置 state,我看到的建议是您应该首先检查组件是否仍在挂载,然后再继续设置 state。
  • 在过滤缺少的使用条款时使用了some数组方法(而不是find),因为您只对布尔值感兴趣。
  • 使用来自响应的数据:设置新状态并找出缺少的使用条款。
  • 路由/推送逻辑现在依赖于missingTermsOfUse(您的代码使用了acceptedTermsofUse,这似乎与您问题的这一部分相矛盾):

如果missingTermsofUse数组的长度为 0,则表示用户已经接受了所有使用条款,我们将他推送到主页,否则我们将他推送到 TermsOfUse 页面。


旁注:您似乎正在使用 TypeScript。如果是这样,通常建议避免使用any,因为您选择退出 TypeScript 提供的主要好处和安全性。

最新更新