質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

Q&A

解決済

1回答

2268閲覧

【React】タイピングゲームで遅延実行/setTimeoutを実装したいです。

panda33

総合スコア5

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

React.js

Reactは、アプリケーションのインターフェースを構築するためのオープンソースJavaScriptライブラリです。

0グッド

0クリップ

投稿2021/12/26 00:56

現在、Reactにて簡単なタイピングゲームを作成しているのですが、遅延実行/setTimeoutの機能を実装しようとしたところエラーが発生し、詰まってしまいました。
お力をお借りできますと幸いです。

前提・実現したいこと

単語の最後の文字を入力して、0.4秒後に次の単語に遷移するようにしたい。

本ゲームでは、未入力の文字は灰色、入力済みの文字は黒色になるよう、cssを書き換えています。
しかし、現状では最後の文字入力が完了した瞬間に、次の単語に遷移してしまうため、最後の文字が黒色(入力完了)となった状態が表示されません。
そこで、遅延実行を導入し、最後の文字を入力して文字が黒色になったあと、0.4秒後に次の単語に遷移するようにしたいと思っています。

発生している問題・エラーメッセージ

下記エラーが発生してしまいます。

TypeError Cannot set properties of undefined (setting 'className') eval /src/App.tsx:84:12 81 | wordTransition(number + 1); 82 | textSpans[0].className = "current-letter"; 83 | for (let i = 1; i < text.length; i++) { > 84 | textSpans[i].className = " waiting-letters"; | ^ 85 | } 86 | }, 400); 87 | }

なお、単語によっては発生しない場合もあります。
現在、「inu」→「kitsune」という順番で、単語が遷移していくのですが、「inu」のときはエラーが出ず「kitsune」の時にはエラーが出てしまいます。

該当のソースコード

▼Codesandbox
https://codesandbox.io/s/musing-wind-x404d?file=/src/App.tsx

React

1import React, { useState, useEffect, useRef } from "react"; 2import "./App.scss"; 3 4export default function App() { 5 //単語データ 6 const wordList = [ 7 "inu", 8 "kitsune", 9 "ookami", 10 "baison", 11 "kitsune", 12 "usagi", 13 "inu", 14 "neko", 15 "ningen", 16 "hitsuji" 17 ]; 18 19 //ルビ 20 const rubiList = [ 21 "いぬ", 22 "きつね", 23 "おおかみ", 24 "ばいそん", 25 "きつね", 26 "うさぎ", 27 "いぬ", 28 "ねこ", 29 "にんげん", 30 "ひつじ" 31 ]; 32 33 //漢字 34 const answerList = [ 35 "犬", 36 "狐", 37 "狼", 38 "バイソン", 39 "狐", 40 "ウサギ", 41 "犬", 42 "猫", 43 "人間", 44 "羊" 45 ]; 46 47 //タイピングに関する部分 48 const [number, setNumber] = useState(0); 49 const [position, setPosition] = useState(0); 50 51 const text = wordList[number]; 52 const answer = answerList[number]; 53 const rubi = rubiList[number]; 54 55 // 単語の変更 56 const wordTransition = (nextNum: number) => { 57 setNumber(nextNum); 58 setPosition(0); 59 60 console.log(nextNum); 61 }; 62 63 const handleKey = (e: React.KeyboardEvent<HTMLDivElement>) => { 64 // 文字の配列を取得 65 let textSpans = document.querySelector("#textbox").children; 66 67 // 入力したキーと現在入力しようとしている文字が一致するとき 68 if (e.key === text[position]) { 69 // 現在の文字を入力済とする 70 textSpans[position].className = "typed-letters"; 71 textSpans[position].classList.remove("current-letter"); 72 73 // まだ入力していない文字があるとき 74 if (position <= text.length - 2) { 75 // 次の位置へ移動 76 textSpans[position + 1].className = "current-letter"; 77 setPosition(position + 1); 78 79 // 全ての文字を入力し終わったとき 80 } else { 81 // 今回追加した実行遅延 82 setTimeout( function() { 83 wordTransition(number + 1); 84 textSpans[0].className = "current-letter"; 85 for (let i = 1; i < text.length; i++) { 86 textSpans[i].className = " waiting-letters"; 87 } 88 }, 400 ); 89 } 90 91 // 間違ったキーを入力したとき 92 } else { 93 textSpans[position].classList.add("typo"); 94 } 95 }; 96 97 //入力エリアにフォーカスを当てる 98 const searchInput = useRef(null); 99 useEffect(() => { 100 searchInput.current.focus(); 101 }); 102 103 //戻るボタン 104 const backButton = () => { 105 wordTransition(number - 1); 106 let textSpans = document.querySelector("#textbox").children; 107 textSpans[position].className = " waiting-letters"; 108 }; 109 110 return ( 111 <> 112 <div 113 ref={searchInput} 114 className="App" 115 onKeyPress={(e) => handleKey(e)} 116 tabIndex={0} 117 > 118 <h1> 119 {/* 問題(漢字) */} 120 <ruby> 121 {answer} 122 <rt>{rubi}</rt> 123 </ruby> 124 </h1> 125 {/* 入力部分 */} 126 <div id="textbox"> 127 <span className="current-letter">{text[0]}</span> 128 {text 129 .split("") 130 .slice(1) 131 .map((char, index) => ( 132 <span className="waiting-letters" key={index}> 133 {char} 134 </span> 135 ))} 136 </div> 137 138 {/* 戻るボタン */} 139 <button 140 onClick={() => { 141 backButton(); 142 }} 143 > 144 戻る 145 </button> 146 <p>{number}/10</p> 147 </div> 148 </> 149 ); 150} 151

試したこと

海外の記事を参考に、下記のようにsetTimeoutをアロー関数にしたり、bind(this)を使用したのですが、うまくいきませんでした。

setTimeout(() => {}, 400); setTimeout(function() {}.bind(this), 400);

単語によって発生しない場合があることとエラー文から、ページのレンダリングのタイミングと今回の遅延実行による処理でタイミングの齟齬(「inu」の文字数に対しての命令と「kitsune」の文字数に対しての命令が混じっているなど)が起きているのかなと考えています。
しかし、JavascriptやReactの知識が乏しく打ち手が分からないため、何かお分かりの方がいらっしゃいましたら、お力をお借りできますと幸いです。

何卒宜しくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

恐らく、textSpansが次の単語になっているからだと思います。なので、classを当てた後に次に進む処理をすると上手く動くかもしれません。

setTimeout( function() { textSpans[0].className = "current-letter"; for (let i = 1; i < text.length; i++) { textSpans[i].className = " waiting-letters"; } wordTransition(number + 1); }, 400 );

投稿2021/12/27 15:05

JJ_1123_I

総合スコア29

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

panda33

2021/12/27 15:29

JJ_1123_I様 ご回答いただきありがとうございます! ご指摘いただいた通り、処理の順番を入れ替えたところエラーもなく、想定していた挙動を実現できました! お手上げ状態だったため本当に助かりました。 重ねて御礼申し上げます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問