· 3 months ago · Jul 07, 2025, 12:20 AM
1import React, { useEffect, useState } from 'react';
2
3const WORD_LIST_API_URL = 'https://api.frontendexpert.io/api/fe/wordle-words';
4const WORD_LENGTH = 5;
5const NUM_ROWS = 6;
6
7function WordleRow({ guess, isFinal, word }) {
8 function decorateGuess(guess, isFinal, word) {
9 const result = [];
10 for (let i = 0; i < WORD_LENGTH; i++) {
11 const tile = {};
12 const char = guess[i] || '';
13 tile.text = char;
14 tile.class = 'tile';
15 // Actually assign a subclass
16 if (isFinal) {
17 if (word[i] === char) {
18 tile.class += ' correct';
19 } else if (word.includes(char)) {
20 tile.class += ' close';
21 } else {
22 tile.class += ' incorrect';
23 }
24 }
25 result.push(tile);
26 }
27 return result;
28 }
29
30 // Do not use curly braces in your arrow function o/w need explicit return
31 return (
32 <div className="line">
33 {decorateGuess(guess, isFinal, word).map((item, index) =>
34 <div key={index} className={item.class}>{item.text}</div>
35 )}
36 </div>
37 );
38}
39
40export default function Wordle() {
41 // Write your code here.
42
43 // Use null so we can conditionally render a default (i.e. before the API has returned)
44 const [word, setWord] = useState(null);
45 const [guess, setGuess] = useState('');
46 const [count, setCount] = useState(0);
47 const [guesses, setGuesses] = useState([]);
48 const [finished, setFinished] = useState(false);
49
50 // Fetch the winning word on component mount.
51 useEffect(() => {
52 const fetchData = async () => {
53 try {
54 const response = await fetch(WORD_LIST_API_URL);
55 const data = await response.json();
56 const index = Math.floor(Math.random() * data.length);
57 const winningWord = data[index];
58 setWord(winningWord.toLowerCase());
59 // For testing
60 // setWord("hello");
61 } catch (error) {
62 console.error('Error fetching data:', error);
63 }
64 }
65
66 fetchData();
67 }, []); // Empty dependency array so it only runs once on mount.
68
69 // Register event listener for the keypress input
70 useEffect(() => {
71 const handleSubmit = () => {
72 if (guess.length < WORD_LENGTH || count === NUM_ROWS || finished) {
73 return;
74 }
75 setFinished(guess === word || count === NUM_ROWS);
76 setGuesses(prevGuesses => [...prevGuesses, {guess: guess, isFinal: true}]);
77 setCount(prevCount => prevCount + 1);
78 setGuess('');
79 }
80
81 const handleBackspace = () => {
82 if (guess.length === 0 || count === NUM_ROWS || finished) {
83 return;
84 }
85 setGuess(prevGuess => prevGuess.slice(0, -1));
86 }
87
88 const handleCharKeyPress = (char) => {
89 if (guess.length === WORD_LENGTH || count === NUM_ROWS || finished) {
90 return;
91 }
92 setGuess(prevGuess => prevGuess + char);
93 }
94
95 const handleKeyPress = (event) => {
96 if (finished) return;
97 if (event.key === 'Enter') {
98 handleSubmit();
99 } else if (event.key === 'Backspace') {
100 handleBackspace();
101 } else { // This is bc we don't have to expect any other input besides lowercase letters.
102 handleCharKeyPress(event.key);
103 }
104 }
105
106 // Very important to use window here as the test env directly dispatches events
107 // to window. In a real browser, the event bubbles up from target -> document -> window
108 // so it wouldn't matter if we added the listener to document or window.
109 // This is why when I had document.addEventListener the code worked locally in the browser
110 // but failed nearly all the unit tests.
111 window.addEventListener('keydown', handleKeyPress);
112 // ALSO VERY IMPORTANT TO REMOVE THE LISTENER, O/W on each re-render
113 // we add a new listener so we have 2, then 3 then 4 listeners...
114 return () => window.removeEventListener('keydown', handleKeyPress);
115 }, [guess, count, guesses]); // VERY IMPORTANT TO SPECIFY THE CORRECT DEPS
116
117 return (
118 <div className="board">
119 {word && Array.from({ length: NUM_ROWS }).map((_, i) =>
120 <WordleRow
121 key={i}
122 guess={count === i ? guess : guesses[i]?.guess || ''}
123 isFinal={count === i ? false : guesses[i]?.isFinal || false}
124 word={word}/>
125 )}
126 </div>
127 );
128}