When I started my project, I made a class that handles the API data. Now I can't figure out how to manage the async to properly display the data. I have a console.log in fetchOpenings that will show my list on console, but it won't display it to the screen. It will display my data if I edit the code.
export class ChessGameFetcher {
async fetchGameArchives(): Promise<void> {
try {
const response = await fetch(this.apiURL);
if (response.ok) {
const data = (await response.json()) as ChessGames;
this.games = data.games;
} else {
throw new Error("Network response was not ok");
}
} catch (error) {
if (error instanceof Error) {
console.error(
"There has been a problem with your fetch operation: ",
error.message
);
} else {
console.error("An unexpected error occurred");
}
}
}
logOpenings(): void {
this.games.forEach((game) => {
let opening = game.eco;
let accuracyWhite = game.accuracies?.white ?? 0;
let accuracyBlack = game.accuracies?.black ?? 0;
let date: Date = new Date(2024, 4);
const extractedDate = this.extractUTCDateTime(game.pgn);
if (extractedDate) {
date = extractedDate;
}
this.processChessString(game.pgn);
const sidePlayed =
game.white.username === this.username ? "white" : "black";
let result =
sidePlayed === "white" ? game.white.result : game.black.result;
result = this.normalizeResult(result);
const match = opening.match(this.regex);
if (match && match[1]) {
opening = match[1].replace(/-$/, "").replace(/-/g, " ");
}
const matchedOpening = this.predefinedOpenings.find((openingName) =>
opening.includes(openingName)
);
if (matchedOpening) {
this.updateOpeningResults(matchedOpening, result, opening);
} else {
console.log(`Unmatched opening: ${opening}`);
}
this.gameInfo.push(
new GameInfo(
result,
opening,
sidePlayed,
game.black.username,
accuracyWhite,
accuracyBlack,
date
)
);
});
}
async init(): Promise<void> {
await this.fetchGameArchives();
this.logOpenings();
this.returnOpeningData();
this.getPercentages();
//console.log(this.user_opening_percentages);
}
}
import React, { useState, useEffect } from "react";
import { ChessGameFetcher } from "./classes/ChessGameFetcher";
import { PercentageInfo } from "./classes/PercentageInfo";
function OpeningForm() {
const [stat, setStat] = useState<PercentageInfo[]>([new PercentageInfo(0, 0, 0, 0), new PercentageInfo(0, 0, 0, 0)]);
const [username, setUsername] = useState("");
const [loading, setLoading] = useState(false);
const [submitted, setSubmitted] = useState(false);
const listStats = stat.map((st, index) => (
<li key={index}>{st.toString()}</li>
));
async function fetchOpenings() {
const gameFetcher = new ChessGameFetcher(username);
const openingStats: PercentageInfo[] = gameFetcher.user_opening_percentages;
console.log(openingStats);
setStat(openingStats);
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
setSubmitted(true); // Trigger the useEffect to fetch data
fetchOpenings();
};
return (
<>
<form onSubmit={handleSubmit}>
<label>
Enter your name:
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</label>
<button type="submit">Submit</button>
</form>
<ul>{listStats}</ul>
</>
);
}
export default OpeningForm;
I've tried adding a useEffect which didn't seem to do anything. I've tried to call await on setSet(), but that doesn't really do anything.
Your use of an async constructor is a bit odd. I suggest refactoring your ChessGameFetcher
class to something along these lines:
export class ChessGameFetcher {
username: string // Not 100% if you need this, but you use it when instantiating your class
constructor(username: string) {
this.username = username
}
async fetchGames() {
await this.fetchGameArchives();
this.logOpenings();
this.returnOpeningData();
this.getPercentages();
}
... // everything else you already have except the init() method
}
The idea of the change is to separate your async functionality into its own method so you can explicitly wait for it to run before calling setStat
. Now you can initialize your class in your React component like this:
async function fetchOpenings() {
const gameFetcher = new ChessGameFetcher(username);
// By using await we ensure we don't try to setStat before all the games are fetched
await gameFetcher.fetchGames();
const openingStats: PercentageInfo[] = gameFetcher.user_opening_percentages;
console.log(openingStats);
setStat(openingStats);
}
Once you've set this up you can load the <ul>
from your stat
variable. Delete listStats
, this variable in your code is only initialized once when your component is rendered and will never work.
return (
<>
<form onSubmit={handleSubmit}>
<label>
Enter your name:
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</label>
<button type="submit">Submit</button>
</form>
<ul>{
stat.map((st, index) => (<li key={index}>{st.toString()}</li>))
}</ul>
</>
)