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>
);
}
This is my code. My problem is mainly with the review titles. They are not displaying on the UI even though I am setting their state in the getReviews() function. There is nothing wrong with my API endpoints I have tested them with Postman and CORS is also handled. The Anilist URL is being displayed correctly after I set its state in the handleSubmit() function even Loading… from loadingReviews state appears but the first time I click on the analyze button it displays the anilist URL but says failed to fetch status 404 for the review titles and the second time I click analyze it displays the anilist URL as before shows Loading… from the loadingReviews state but doesn’t display anything. From what I have researched it looks like the problem is with the asynchronous nature of useState but I am not really able to understand how to solve this problem. When I console log the reviewTitles it works albeit after clicking the second time(the first time I get a failed to fetch status 404 as I said before) but nothing is displayed on the frontend as I expect. How do I solve this problem? Any help would be appreciated greatly.
I have tried setting reviewTitles using a callback function but that did not work either. Console logging helped me see that there was a problem but I do not understand why nothing displays the first time I click analyze even though I console logged directly after setting the state. I thought of using useEffect() but this does not seem to be the correct usecase for it.
2
Answers
Guys I have found an answer to my question I added a useEffect() hook to run getReviews() function whenever there is a change in the anilistURL instead of running it directly from the handleSubmit function and mapped the review titles using a key prop(thanks to Nick Parsons) which seemed to solve the problem for me. Here is the working code:
In your case when data is updated there is no event listener for this one . as a example you have updated data but not reflect in your code .
first define state variable then update it
add use effect event listener to listener and update component.
Here’s an updated version of your React component, with a proper state update and an useEffect hook to listen for changes and update the component when necessary:
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]);
const handleSubmit = async () => {
try {
setLoading(true);
setReviewTitles([]);
setAnilistURL("");
setErrorMsg("");
};
const getReviews = async () => {
try {
setLoadingReviews(true);
setErrorMsg("");
};
return (
{loading && Loading…}
{errorMsg && {errorMsg}}
{anilistURL && (
Anilist URL:
{anilistURL}
)}
{loadingReviews && Loading…}
{reviewTitles.length > 0 && (
reviewTitles.map((reviewTitle, index) => (
{reviewTitle}
))
)}
Enter anime to be analyzed
<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)}
/>
Analyze
);
}