import React, { useState } from "react";
export default function Recommend() {
const [animeName, setAnimeName] = useState("");
const [anilistURL, setanilistURL] = useState("");
const [errorMsg, setErrorMsg] = useState("");
const [loading, setLoading] = useState(false);
const [loadingReviews, setLoadingReviews] = useState(false);
const [reviewTitles, setreviewTitles] = useState([]);
const handleSubmit = async () => {
try {
setLoading(true);
setreviewTitles([]);
setanilistURL("");
setErrorMsg("");
const response = await fetch("http://localhost:8000/get-anilist-url", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
anime_title: animeName,
}),
});
if (!response.ok) {
const errorData = await response.json();
setErrorMsg(errorData.error || "Unable to get response");
}
const data = await response.json();
setanilistURL(data.anilist_url || "No URL found");
setErrorMsg("");
} catch (error) {
setErrorMsg("An error occurred: " + error.message);
} finally {
setLoading(false);
getReviews();
}
};
const getReviews = async () => {
try {
setLoadingReviews(true);
setErrorMsg("");
const anilistReviewsURL = anilistURL + "/reviews";
const response = await fetch(
"http://localhost:3000/api/v1/scrape/get-review-titles",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ reviewsLink: anilistReviewsURL }),
}
);
const data = await response.json();
if (!response.ok) {
setErrorMsg(data.error || "Unable to get response");
}
setreviewTitles((prevTitles) => [
...prevTitles,
data.reviewTitles
]);
setErrorMsg("");
} catch (error) {
setErrorMsg("An error occurred: " + error);
}
finally {
setLoadingReviews(false);
}
};
return (
<div className="flex flex-col h-screen bg-slate-700">
<div className="flex-grow bg-slate-600 p-5 font-mono">
{loading && <div className="text-white">Loading...</div>}
{errorMsg && <div className="text-red-500">{errorMsg}</div>}
{anilistURL && (
<div className="text-yellow-100">
<p>Anilist URL:</p>
<a href={anilistURL} target="_blank" rel="noopener noreferrer">
{anilistURL}
</a>
</div>
)}
{loadingReviews && <div className="text-white">Loading...</div>}
{reviewTitles && (
reviewTitles.map((reviewTitle) => {
(
<div className = "text-yellow-500 pt-3">
{reviewTitle}
</div>
)
})
)}
</div>
<div className="p-4 flex items-center justify-between bg-slate-800">
<h3 className="text-gray-500 text-3xl font-mono">
Enter anime to be analyzed
</h3>
<textarea
type="text"
className="bg-slate-700 font-mono text-white text-2xl p-3 border-none outline-none w-full rounded-full"
placeholder="Enter your prompt..."
value={animeName}
onChange={(e) => setAnimeName(e.target.value)}
/>
<button
className="bg-slate-700 font-mono text-white text-2xl p-4 rounded-full hover:bg-slate-600 transition duration-200 ml-5"
onClick={handleSubmit}
>
Analyze
</button>
</div>
</div>
);
}
这是我的代码。我的问题主要是评论标题。即使我在 getReviews() 函数中设置它们的状态,它们也不会显示在 UI 上。我的 API 端点没有任何问题,我已经使用 Postman 对其进行了测试,并且 CORS 也得到了处理。在我在handleSubmit()函数中设置其状态后,Anilist URL正确显示,甚至出现正在加载...从loadingReviews状态,但我第一次单击分析按钮时,它显示anilist URL,但说无法获取状态404评论标题,第二次单击分析时,它会像以前一样显示 anist URL,从加载评论状态显示正在加载...,但不显示任何内容。根据我的研究,问题似乎出在 useState 的异步性质上,但我真的不明白如何解决这个问题。当我通过控制台记录 reviewTitles 时,它可以工作,尽管在第二次单击后(第一次我收到无法获取状态 404,如我之前所说),但前端没有像我预期的那样显示任何内容。我该如何解决这个问题?任何帮助将不胜感激。
我尝试使用回调函数设置 reviewTitles,但这也不起作用。控制台日志记录帮助我发现存在问题,但我不明白为什么第一次单击“分析”时没有显示任何内容,即使我在设置状态后直接进行控制台日志记录。我想过使用 useEffect() 但这似乎不是它的正确用例。
伙计们,我已经找到了我的问题的答案,我添加了一个 useEffect() 钩子,以便在 anistURL 发生更改时运行 getReviews() 函数,而不是直接从 handleSubmit 函数运行它,并使用 key prop 映射评论标题(感谢尼克·帕森斯)这似乎为我解决了问题。这是工作代码:
import React, { useState, useEffect } from "react";
export default function Recommend() {
const [animeName, setAnimeName] = useState("");
const [anilistURL, setAnilistURL] = useState("");
const [errorMsg, setErrorMsg] = useState("");
const [loading, setLoading] = useState(false);
const [loadingReviews, setLoadingReviews] = useState(false);
const [reviewTitles, setReviewTitles] = useState([]);
useEffect(() => {
if (anilistURL) {
getReviews(anilistURL);
}
}, [anilistURL]);
const handleSubmit = async () => {
try {
setLoading(true);
setAnilistURL("");
setReviewTitles([]);
setErrorMsg("");
const response = await fetch("http://localhost:8000/get-anilist-url", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
anime_title: animeName,
}),
});
if (!response.ok) {
const errorData = await response.json();
setErrorMsg(errorData.error || "Unable to get response");
setLoading(false);
return;
}
const data = await response.json();
setAnilistURL(data.anilist_url || "No URL found");
} catch (error) {
setErrorMsg("An error occurred: " + error.message);
setLoading(false);
}
};
const getReviews = async (url) => {
try {
setLoadingReviews(true);
setErrorMsg("");
const anilistReviewsURL = url + "/reviews";
const response = await fetch(
"http://localhost:3000/api/v1/scrape/get-review-titles",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ reviewsLink: anilistReviewsURL }),
}
);
if (!response.ok) {
const errorData = await response.json();
setErrorMsg(errorData.error || "Unable to get response");
setLoadingReviews(false);
return;
}
const data = await response.json();
setReviewTitles(data.reviewTitles || []);
} catch (error) {
setErrorMsg("An error occurred: " + error.message);
} finally {
setLoadingReviews(false);
}
};
return (
<div className="flex flex-col h-screen bg-slate-700">
<div className="flex-grow bg-slate-600 p-5 font-mono">
{loading && <div className="text-white">Loading...</div>}
{errorMsg && <div className="text-red-500">{errorMsg}</div>}
{anilistURL && (
<div className="text-yellow-100">
<p>Anilist URL:</p>
<a href={anilistURL} target="_blank" rel="noopener noreferrer">
{anilistURL}
</a>
</div>
)}
{loadingReviews && <div className="text-white">Loading reviews...</div>}
{reviewTitles.length > 0 && (
<div>
<h3 className="text-white">Review Titles:</h3>
{reviewTitles.map((reviewTitle, index) => (
<div key={index} className="text-yellow-500 pt-3">
{reviewTitle}
</div>
))}
</div>
)}
</div>
<div className="p-4 flex items-center justify-between bg-slate-800">
<h3 className="text-gray-500 text-3xl font-mono">
Enter anime to be analyzed
</h3>
<textarea
type="text"
className="bg-slate-700 font-mono text-white text-2xl p-3 border-none outline-none w-full rounded-full"
placeholder="Enter your prompt..."
value={animeName}
onChange={(e) => setAnimeName(e.target.value)}
/>
<button
className="bg-slate-700 font-mono text-white text-2xl p-4 rounded-full hover:bg-slate-600 transition duration-200 ml-5"
onClick={handleSubmit}
>
Analyze
</button>
</div>
</div>
);
}