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>
);
}
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 div
s):
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>