skip to Main Content

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(/[^ws!?]/g, '');
            console.log(wordStrippedMulti);
            setWords([...words, wordStrippedMulti as string])
        }
    } else {
        const wordStrippedSingle = word.wordValue.replace(/[^ws!?]/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?

3

Answers


  1. This is happening because of the async behavior of setState.

    That means your setWords is async here and you are using it in a loop. And that is why it is only setting the last iteration value of the loop.

    So just change the if block logic to this:

    if (word.wordValue.includes(',')) {
            const wordParsed = word.wordValue.split(',');
            let tempWords = []; 
            for (const wordParsedIndividual in wordParsed) {
                const wordStrippedMulti = wordParsed[wordParsedIndividual].replace(/[^ws!?]/g, '');
                tempWords = [...tempWords, wordStrippedMulti]; 
                
            }
            setWords(tempWords); 
      }
    

    Understand more about setState async behavior: https://stackoverflow.com/a/36087156/5939058

    Login or Signup to reply.
  2. Because setState is asynchronous. You can fix it like this

    setWords(prevWords => ([...prevWords, wordStrippedMulti as string]))
    
    Login or Signup to reply.
  3. setWords should not be used inside a for loop as it is not an immediate command, and there’s a delay involved.

    As explained in the docs

    The words won’t have the updated value, and that’s the reason it will only show the last inserted element inside.
    Instead, you can maintain a variable and add it to the state once the loop ends executing.

    const newWords = []; 
        for (const wordParsedIndividual in wordParsed) {
            const wordStrippedMulti = wordParsed[wordParsedIndividual].replace(/[^ws!?]/g, '');
            newWords = [...newWords, wordStrippedMulti]; 
            
        }
        setWords(newWords); 
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search