I'm learning React and I know this subject has been covered by many questions, but they all are focused on the asynchronous nature of useState
. I'm not sure if that's what's happening here. I also tried a version in combination with useEffect
, and the result was the same.
I have a component where I'm listening to keypresses - user is trying to guess a word. Once the word is guessed, the word
object is supposed to be replaced with another one and a new puzzle begins.
The component renders using the correct state (characters of the new word), but when trying the first guess of the second puzzle, the word
object is still the original state object.
How can I update this word
object correctly?
Steps to reproduce in the readme.md:
const WordFrame = () => {
const [word, setWord] = useState(() => new Word 'apple');
const [renderedCharacters, setRenderedCharacters] = useState(
word.renderedCharacters
);
const keyDownHandler = (e: KeyboardEvent): void => {
console.log(e.key);
if (letters.includes(e.key)) {
const correctGuess = word.processGuess(e.key);
if (correctGuess) {
setRenderedCharacters([...word.renderedCharacters]);
// moving the following if block into useEffect with dependency on word.isGuessed and renderedCharacters doesn't help
if (word.isGuessed) {
const newWord: Word = new Word('banana');
setWord(newWord);
setRenderedCharacters(newWord.renderedCharacters);
}
}
}
};
useEffect(() => {
document.addEventListener('keydown', keyDownHandler);
return () => document.removeEventListener('keydown', keyDownHandler);
}, []);
// useEffect(() => {
// if (word.isGuessed) {
// const newWord = new Word('banana);
// setWord(newWord);
// setRenderedCharacters(newWord.renderedCharacters);
// }
// }, [word.isGuessed, renderedCharacters]);
return (
<div className='word-frame'>
{renderedCharacters.map((c) => (
<LetterFrame characterValue={c.value} key={c.id} />
))}
</div>
);
};
export default WordFrame;
Word.ts
class Word {
private readonly _unguessedCharacter: string = ' ';
private readonly _characters: string[] = [];
private _guessingIndex = 0;
private _renderedCharacters: Character[] = [];
public get renderedCharacters() {
return this._renderedCharacters;
}
private _isGuessed: boolean = false;
public get isGuessed(): boolean {
return this._isGuessed;
}
constructor(wordString: string) {
console.log(`Word.constructor called with parameter: ${wordString}`);
this._characters = wordString.split('');
this.setRenderedCharacters();
}
public processGuess(letter: string): boolean {
const isSuccessfulGuess = (): boolean =>
this._characters[this._guessingIndex].toLowerCase() ===
letter.toLowerCase();
const successful = isSuccessfulGuess();
if (successful) {
this._guessingIndex++;
this.setRenderedCharacters();
}
if (this._guessingIndex > this._characters.length - 1) {
this._isGuessed = true;
}
return successful;
}
private setRenderedCharacters(): void {
this._renderedCharacters = [];
this._characters.forEach((c, i): void => {
if (i >= this._guessingIndex) {
this._renderedCharacters.push(
new Character(this._unguessedCharacter)
);
} else {
this._renderedCharacters.push(new Character(c));
}
});
}
}
export default Word;