Home > Software engineering >  cleaning setstate in react
cleaning setstate in react

Time:12-06

I have a function called getLines which tries to find out when text is going to wrap in another line it is async and it is so time-consuming and I have an API that gives a string with a given length (query)

import { useState, useEffect } from "react";
import getLines from "./getline"; //this function take time according to input

export default function App() {
  const [text, setText] = useState<string>("");
  const [arr, setArr] = useState<string[] | "error" | undefined>([]);
  const [time, setTime] = useState<100 | 200 | 300 | "un">("un");
  const [pending, setPending] = useState(false);
  useEffect(() => {
    if (time !== "un") {
      (async () => {
        setPending(true);
        let res = await fetch(
          `https://random-word-genrator.vercel.app/words?w=${time}`
        );
        let { response } = await res.json();
        let a = await getLines(800, response, "1.2rem", "3px");
        setArr(a);
        setText(response);
        setPending(false);
      })();
    }
  }, [time]);

  return (
    <div>
      <div style={{ display: "flex", gap: "1rem" }}>
        <div
          onClick={() => {
            setTime(100);
          }}
        >
          100
        </div>
        <div
          onClick={() => {
            setTime(200);
          }}
        >
          200
        </div>
        <div
          onClick={() => {
            setTime(300);
          }}
        >
          300
        </div>
      </div>
      {pending ? (
        <div>loading</div>
      ) : (
        <div>
          <div>value:={text}</div>
          <div>array:={JSON.stringify(arr)}</div>
          <div>length:={text.split(" ").length}</div>
        </div>
      )}
    </div>
  );
}

video of problem

you can see in video that when I click 100 it give me 100 words when I click to 200 it give me 200 words but when I click multiple button it gives me different length then what I last clicked can anyone tell me how can i remove this problem?
sanboxLink:- sendbox link
**recreate error by quickly clicking 300 and 100 with this order 300--quickly-->100

CodePudding user response:

The problem you are experiencing is caused by the fact that the useEffect hook is asynchronous, which means that multiple calls to the hook can be in-flight at the same time. This can lead to unpredictable behavior, such as the one you are seeing where the text and arr state variables are not updated as expected.

To fix this issue, you can add a useRef to keep track of the current time value and use that value to determine whether or not to update the state variables in the useEffect hook. This will ensure that the state variables are only updated when the time value changes, and not when multiple calls to the useEffect hook are in-flight.

CodePudding user response:

Clicking multiple times invokes multiple operations. Since the operations are asynchronous, they can complete in any order.

If you don't want the user to be able to invoke multiple simultaneous operations, prevent/ignore the click when an operation is still pending. The pending state value you already have seems like a reasonable way to check that.

For example, you can display "loading" instead of the "buttons" (well, clickable divs):

return (
<div>
  {pending ? (
    <div>loading</div>
   ) : (
    <div style={{ display: "flex", gap: "1rem" }}>
      <div onClick={() => { setTime(100); }} >
        100
      </div>
      <div onClick={() => { setTime(200); }} >
        200
      </div>
      <div onClick={() => { setTime(300); }} >
        300
      </div>
    </div>
    <div>
      <div>value:={text}</div>
      <div>array:={JSON.stringify(arr)}</div>
      <div>length:={text.split(" ").length}</div>
    </div>
  )}
  </div>
);

Alternatively, you could keep the UI but just ignore the click:

<div onClick={() => {
  if (!pending) {
    setTime(100);
  }
}} >
  100
</div>
  • Related