UIをアップグレードしよう
💫 UI の仕上げ
NFTキャラクターをMintしたり、ボスのデータを取得したりするときに、ローディングマークをUIに表示していきましょう。
これから、下記のケースにローディングマーク実装していきます。
1. App.js : ユーザーがNFTキャラクターを持っているかフロントエンドが確認している状況
2. SelectCharacterコンポーネント : ユーザーがNFTキャラクターをMintするのをフロントエンドが待機している状況
3. Arenaコンポーネント : 攻撃が終了するのをフロントエンドが待機している状況
client/src/ComponentsフォルダにLoadingIndicatorコンポーネントが格納されています。
このレッスンでは、このLoadingIndicatorコンポーネントを使っていきます。
🔁 App.jsにローディングマークを追加する
1つ目のケース、「ユーザーがNFTキャラクターを持っているかフロントエンドが確認している状況」で、Webアプリケーションにローディングマークを表示していきましょう。
まず、App.jsを開き、const [characterNFT, setCharacterNFT] = useState(null);の直下に下記を追加しましょう。
// ロード状態を初期化します。
const [isLoading, setIsLoading] = useState(false);
次に、コントラクトからcheckIfUserHasNFT関数を呼び出すなど、非同期操作を実行している際に、ロード状態を設定する実装を行います。
setIsLoading(true);を、下記2つのuseEffectsに追加しましょう。
// ページがロードされたときに useEffect()内の関数が呼び出されます。
useEffect(() => {
// ページがロードされたら、即座にロード状態を設定するようにします。
setIsLoading(true);
checkIfWalletIsConnected();
}, []);
// ページがロードされたときに useEffect()内の関数が呼び出されます。
useEffect(() => {
// スマートコントラクトを呼び出す関数です。
const fetchNFTMetadata = async () => {
console.log("Checking for Character NFT on address:", currentAccount);
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const gameContract = new ethers.Contract(
CONTRACT_ADDRESS,
myEpicGame.abi,
signer
);
const txn = await gameContract.checkIfUserHasNFT();
if (txn.name) {
console.log("User has character NFT");
setCharacterNFT(transformCharacterData(txn));
} else {
console.log("No character NFT found");
}
// ユーザーが保持している NFT の確認が完了したら、ロード状態を false に設定します。
setIsLoading(false);
};
if (currentAccount) {
console.log("CurrentAccount:", currentAccount);
fetchNFTMetadata();
}
}, [currentAccount]);
次に、App.jsの先頭に下記を追加して、LoadingIndicatorをインポートしてください。
import LoadingIndicator from "./Components/LoadingIndicator";
次に、renderContent関数の先頭に、下記を追加しましょう。
// アプリがロード中の場合は、LoadingIndicator をレンダリングします。
if (isLoading) {
return <LoadingIndicator />;
}
この処理により、Webアプリケーションがコントラクトからデータを読み込んでいる間は、ローディングマークが表示されます。
次に、checkIfWalletIsConnectedに下記のように更新して、フロントエンドがユーザーがMetaMaskを持っているか確認している際に、ローディングマークを表示させましょう。
// ユーザーが MetaMask を持っているか確認します。
const checkIfWalletIsConnected = async () => {
try {
const { ethereum } = window;
if (!ethereum) {
console.log("Make sure you have MetaMask!");
// 次の行で return を使用するため、ここで isLoading を設定します。
setIsLoading(false);
return;
} else {
console.log("We have the ethereum object", ethereum);
// accounts にWEBサイトを訪れたユーザーのウォレットアカウントを格納します。
// (複数持っている場合も加味、よって account's' と変数を定義している)
const accounts = await ethereum.request({ method: "eth_accounts" });
// もしアカウントが一つでも存在したら、以下を実行。
if (accounts.length !== 0) {
// account という変数にユーザーの1つ目(= Javascript でいう0番目)のアドレスを格納
const account = accounts[0];
console.log("Found an authorized account:", account);
// currentAccount にユーザーのアカウントアドレスを格納
setCurrentAccount(account);
} else {
console.log("No authorized account found");
}
}
} catch (error) {
console.log(error);
}
//すべての関数ロジックの後に、state プロパティを解放します。
setIsLoading(false);
};
ウォレットの接続を解除すると、ローディングマークが表示されるはずです。ウォレット接続ボタンが表示されるように、isLoading状態のプロパティを解放する(=falseにする)必要があります。
🔁 SelectCharacterコンポーネントにローディングマークを追加する
2つ目のケース、「ユーザーがNFTキャラクターをMintするのをフロントエンドが待機している状況」で、Webアプリケーションにローディングマークを表示していきましょう。
まず、client/src/Components/SelectCharacter/index.jsの先頭に、下記を追加しましょう。
import LoadingIndicator from "../../Components/LoadingIndicator";
次に、SelectCharacter/index.jsの中に記載されたconst [gameContract, setGameContract] = useState(null);の直下に、const [mintingCharacter, setMintingCharacter] = useState(false);を追加しましょう。
- 下記を参照してください。
//NFT キャラクターのメタデータを保存する状態変数を初期化します。
const [characters, setCharacters] = useState([]);
// コントラクトのデータを保有する状態変数を初期化します。
const [gameContract, setGameContract] = useState(null);
// Minting の状態保存する状態変数を初期化します。
const [mintingCharacter, setMintingCharacter] = useState(false);
ここでは、App.jsでisLoadingを初期化した時と同様に、NFTのMinting状態を保存するmintingCharacterという状態変数を初期化しています。
次に、mintCharacterNFTActionの中に、setMintingCharacterを3つ設置していきます。
- 下記を参考にしてください。
// NFT を Mint します。
const mintCharacterNFTAction = (characterId) => async () => {
try {
if (gameContract) {
// Mint が開始されたら、ローディングマークを表示する。
setMintingCharacter(true);
console.log("Minting character in progress...");
const mintTxn = await gameContract.mintCharacterNFT(characterId);
await mintTxn.wait();
console.log("mintTxn:", mintTxn);
// Mint が終了したら、ローディングマークを消す。
setMintingCharacter(false);
}
} catch (error) {
console.warn("MintCharacterAction Error:", error);
// エラーが発生した場合も、ローディングマークを消す。
setMintingCharacter(false);
}
};
最後に、NFTがMintされている間にローディングマークを表示するHTMLを実装しましょう。
SelectCharacter/index.jsの中にあるreturn();の中身を下記のように更新してください。
return (
<div className="select-character-container">
<h2>⏬ 一緒に戦う NFT キャラクターを選択 ⏬</h2>
{characters.length > 0 && (
<div className="character-grid">{renderCharacters()}</div>
)}
{/* mintingCharacter = trueの場合のみ、ローディングマークを表示します。*/}
{mintingCharacter && (
<div className="loading">
<div className="indicator">
<LoadingIndicator />
<p>Minting In Progress...</p>
</div>
</div>
)}
</div>
);
SelectCharacter.cssにも下記のCSSを追加しましょう。
client/src/Components/SelectCharacterフォルダの中にSelectCharacter.cssが格納されています。
.select-character-container .loading {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 75px;
}
.select-character-container .loading .indicator {
display: flex;
}
.select-character-container .loading .indicator p {
font-weight: bold;
font-size: 28px;
padding-left: 5px;
}
.select-character-container .loading img {
width: 450px;
padding-top: 25px;
}
上記の実装はフロントエンドに下記のように反映されます。
