Home > Software design >  State incrementing by more than expected
State incrementing by more than expected

Time:04-14

tearing my hair out here so would absolutely love some help.

I'm using ReactJS to make a language learning app, and have a parent component with to host a series of questions. The questions themselves are all contained in a child component called TestComponents, and when users get the a question right it should increment a state counter called index by one, then render the questions with this: {questions[index]}

But two of the five question components I've made are incrementing the count by two instead of one.

Each of the question components has this prop passed to it: handleCorrect={() => handleCorrect()}

Which should call this function:

  const handleCorrect = () => {
    setCorrect(true);
    setIndex(prevIndex => prevIndex   1);

    setTimeout(() => {
      setCorrect(false);
    }, 2000);
  };

So I can't see anywhere in there which should be causing that function to be called twice. And if I put a console.log in it, that only appears once in my console - not twice, so it seems like this is incrementing by two instead of by one, rather than just calling the function twice.

As I say, three of the question components work fine, but the two that are breaking it are the below - apologies, because they're both quite big, but I've scoured them to try and work out what is breaking it and just cannot spot it, so I'm worried that if I only put what seem to me like the relevant parts of them, I'm worried I'll miss the part that's actually breaking it!

I have, though, put a comment saying where I am intending for it to call that function in to highlight them.

The first is this:

export const Leniter = ({
  sentence,
  correctSentence,
  handleCorrect,
  handleSubmit,
  header,
  topic,
}) => {
  const [sentenceSplit, setSentenceSplit] = useState(sentence.split(" "));

  const [submitted, setSubmitted] = useState(false);
  const handleSubmitCheck = () => {
    setSubmitted(true);
  };

  const handleClick = (word, index) => {
    setSubmitted(false);

    if (word.slice(1, 2) == "h") {
      let sentence2 = [...sentenceSplit];
      let word2 = word.slice(0, 1)   word.slice(2);
      sentence2[index] = word2;
      setSentenceSplit(sentence2);
    } else {
      let sentence2 = [...sentenceSplit];
      let word2 = word.slice(0, 1)   "h"   word.slice(1);
      sentence2[index] = word2;
      setSentenceSplit(sentence2);
    }
  };


//********Call the handleCorrect prop here*********
  //handleCorrect / incorrect on submit
  if ((!handleSubmit || (handleSubmit && submitted)) &&
    sentenceSplit.join(" ") == correctSentence) 
    {
      handleCorrect(topic);
      setSubmitted(false)
      setSentenceSplit(sentence.split(" "))
    }

  handleSubmit &&
    submitted &&
    sentenceSplit.join(" ") !== correctSentence &&
    console.log("incorrect");

  return (
    <div className="testComponent">
      {header && header == "default" ? (
        <h3>
          Add or remove the <i>h</i> to make the correct form of the words:
        </h3>
      ) : (
        header && <h3>{header}</h3>
      )}

      <Paper
        sx={{
          display: "flex",
          justifyContent: "center",
          flexWrap: "wrap",
          listStyle: "none",
          minHeight: "20px",
          backgroundColor:
            (!handleSubmit || (handleSubmit && submitted)) &&
            sentenceSplit.join(" ") == correctSentence
              ? "lightgreen"
              : handleSubmit &&
                submitted &&
                sentenceSplit.join(" ") !== correctSentence
              ? "red"
              : "aliceblue",
          p: 0.5,
          m: 0,
          borderRadius: "5px 5px 5px 5px",
          border: "1px solid grey",
        }}
      >
        {sentenceSplit.map((word, index) => {
          return (
            <Chip
              onClick={(word) => handleClick(word.target.textContent, index)}
              label={word}
              sx={{ marginLeft: "5px" }}
            />
          );
        })}
      </Paper>

      {handleSubmit && (
        <SubmitBtn variant="contained" onClick={() => handleSubmitCheck()}>
          Submit
        </SubmitBtn>
      )}
    </div>
  );
};

And the second is this:

export const AccentSelector = ({
  sentence,
  handleCorrect,
  handleSubmit,
  header,
  topic,
}) => {
  const [submitted, setSubmitted] = useState(false);

  let accents = "aàeèiìoòuùAÀEÈIÌOÒUÙ";

  const [selectValue, setSelectValue] = useState([{}]);

  const handleChange = (event, index) => {
    setSubmitted(false);
    setCorrect([]);
    setSelectValue([
      ...selectValue,
      { index: index, value: event.target.value },
    ]);
  };

  //handleCorrect/incorrect
  const [correct, setCorrect] = useState([]);

  const handleSubmitCheck = () => {
    setCorrect([]);
    setSubmitted(true);
  };

  console.log(submitted, correct)

//********Call the handleCorrect prop here*********
  if (
    submitted &&
    correct.length > 0 &&
    correct.includes("true") &&
    !correct.includes("false")
  ) {
    handleCorrect();
    setSelectValue([{}]);
    setCorrect([])
  }

  return (
    <div className="testComponent">
      {header && header == "default" ? (
        <h3>Select the correct versions of the vowels:</h3>
      ) : (
        header && <h3>{header}</h3>
      )}
      <h3>
        {sentence.split("").map((letter, index) => {
          if (accents.split("").includes(letter)) {
            let val =
              selectValue.filter((arrayVal) => arrayVal.index == index).length >
              0
                ? selectValue.filter((arrayVal) => arrayVal.index == index)[
                    selectValue.filter((arrayVal) => arrayVal.index == index)
                      .length - 1
                  ].value
                : "?";

            let bgColor =
              (!handleSubmit || (handleSubmit && submitted)) &&
              selectValue.filter((arrayVal) => arrayVal.index == index).length >
                0 &&
              selectValue.filter((arrayVal) => arrayVal.index == index)[
                selectValue.filter((arrayVal) => arrayVal.index == index)
                  .length - 1
              ].value == letter
                ? "lightgreen"
                : (!handleSubmit || (handleSubmit && submitted)) &&
                  selectValue.filter((arrayVal) => arrayVal.index == index)
                    .length > 0 &&
                  selectValue.filter((arrayVal) => arrayVal.index == index)[
                    selectValue.filter((arrayVal) => arrayVal.index == index)
                      .length - 1
                  ].value != "?"
                ? "red"
                : "aliceblue";

            //push to Correct array
            if (bgColor == "lightgreen" && !correct.includes("true")) {
              setCorrect([...correct, "true"]);
            }

            if (
              (bgColor == "red" || bgColor == "aliceblue") &&
              !correct.includes("false")
            ) {
              setCorrect([...correct, "false"]);
            }

            switch (letter) {
              case "a":
              case "à":
                return (
                  <>
                    <Select
                      onChange={(event) => handleChange(event, index)}
                      value={val}
                      autoWidth
                      label={index}
                      size="small"
                      style={{
                        backgroundColor: bgColor,
                      }}
                    >
                      <MenuItem value="?">?</MenuItem>
                      <MenuItem value="a">a</MenuItem>
                      <MenuItem value="à">à</MenuItem>={" "}
                    </Select>
                  </>
                );
                break;
              case "è":
              case "e":
                return (
                  <>
                    <Select
                      onChange={(event) => handleChange(event, index)}
                      value={val}
                      autoWidth
                      label={index}
                      size="small"
                      style={{
                        backgroundColor: bgColor,
                      }}
                    >
                      <MenuItem value="?">?</MenuItem>
                      <MenuItem value="e">e</MenuItem>
                      <MenuItem value="è">è</MenuItem>={" "}
                    </Select>
                  </>
                );
                break;
              default:
                return <>broken</>;
            }
          } else {
            return <>{letter}</>;
          }
        })}
      </h3>
      {handleSubmit && (
        <SubmitBtn variant="contained" onClick={() => handleSubmitCheck()}>
          Submit
        </SubmitBtn>
      )}
    </div>
  );
};

Thank you so much for any help!

CodePudding user response:

You should not call any logic to update state value in the component body.

e.g. You changed state twice by setSubmitted and setSentenceSplit. So handleCorrect may be called twice because of state changes above.

    if ((!handleSubmit || (handleSubmit && submitted)) &&
    sentenceSplit.join(" ") == correctSentence) 
    {
      handleCorrect(topic);
      setSubmitted(false)
      setSentenceSplit(sentence.split(" "))
    }

You can do it in useEffect.

useEffect(() => {
if ((!handleSubmit || (handleSubmit && submitted)) &&
    sentenceSplit.join(" ") == correctSentence) 
    {
      handleCorrect(topic);
      setSubmitted(false)
      setSentenceSplit(sentence.split(" "))
    }

}, [submitted, handleCorrect, sentenceSplit, handleSubmit])

  • Related