Search code examples
reactjsfor-loopreact-hookssetstate

React app can't set useState variables in for loop


I have the following React code:

import { useState } from 'react';
import './App.css';
import Layout from './components/Layout';

interface WordsPayload {
  listedwords: string[]
}

function App() {
  const [word, setWord] = useState({
    wordValue: ""
  });
  const [words, setWords] = useState<string[]>([]);

  const handleWordChange = (event: { target: { name: any; value: any; }; }) => {
    setWord({ ...word, [event.target.name]: event.target.value });
  };

  const handleWordSubmit = (event: { preventDefault: () => void; }) => {
    event.preventDefault();
    setWords([...words, word.wordValue as string]);
    setWord({ wordValue: "" });
  };

  const handleSubmit = async (event: { preventDefault: () => void; }) => {
    event.preventDefault();
    const jsnWords: WordsPayload =
    {
      listedwords: words
    }
    const findAnagrams = await fetch('http://localhost:3000/findanagrams', {
      method: 'POST',
      headers: {'Content-Type':'application/json'},
      body: JSON.stringify(jsnWords)
    });
    const anagramsFound = await findAnagrams.json();
    console.log(anagramsFound);
    return findAnagrams;
  };

  return (
    <Layout>
      <section className="container">
        <div className="flex-1 space-y-4">
          <h1 className="text-4xl font-bold">
            Anagram Finder
          </h1>
          <p className="max-w-xl leading-relaxed">
            Type words consisting of only alphanumeric characters
            into the text box. When you finish a word click "Add"
            After you finish adding words click submit.
          </p>
          <div className="items-center justify-center">
          <button onClick={handleSubmit}>
            Submit
          </button>
          </div>
        </div>
        <form onSubmit={handleWordSubmit}>
          <div className="mb-6">
            <label className="block mb-2">Type word here</label>
            <input
              type="text"
              name="wordValue"
              placeholder="Name"
              value={word.wordValue}
              onChange={handleWordChange}
            />
          </div>
          <button type="submit" className="dark:focus:ring-blue-800">Add Word</button>
        </form>
      </section>
      <section className="container">
        <div className="flex-1 space-y-4"></div>
        {words.map(function(d, idx){
          return (<span key={idx}>{d}</span>)
        })}
      </section>
    </Layout>
  )
}

export default App  

I have a text box that pushes inputs words into words, which is a useState array, on button press. This is what it looks like in action:
enter image description here

I modified handleWordSubmit to allow for adding several words at a time to the words useState array by separating input string at , values and pushing all of these array values one-at-a-time.

Now it looks like this:

const handleWordSubmit = (event: { preventDefault: () => void; }) => {
    event.preventDefault();
    if (word.wordValue.includes(',')) {
        const wordParsed = word.wordValue.split(',');
        for (const wordParsedIndividual in wordParsed) {
            const wordStrippedMulti = wordParsed[wordParsedIndividual].replace(/[^\w\s!?]/g, '');
            console.log(wordStrippedMulti);
            setWords([...words, wordStrippedMulti as string])
        }
    } else {
        const wordStrippedSingle = word.wordValue.replace(/[^\w\s!?]/g, '');
        setWords([...words, wordStrippedSingle as string]);
    }
    setWord({ wordValue: "" });
};

So I thought that this would separate the string into an array at the ,s, loop over the array, and push every individual string to the words useState array. But it doesn't... This is what happened:
enter image description here

Notice that the string is properly separated at the ,s and the code does loop over every item in the array because they are successfully printed to the console. The problem is that only the last item is actually added to the words useState array. So why aren't all of the split string array items added to words?


Solution

  • Because setState is asynchronous. You can fix it like this

    setWords(prevWords => ([...prevWords, wordStrippedMulti as string]))