1

I have this useEffect function in react component. I am calling api videoGridState here. Now what is happening here it is calling my api 2 times one at intitial page reaload and second one when count is changing. I want it to be called single time when page reloads. But also when streamSearchText changes

  const [count, setCount] = useState(0);
  const [streamSearchText, setStreamSearchText] = useState("");
  useEffect(() => {
    videoGridState();
  }, [count]);

  useEffect(() => {
    const delayDebounceFn = setTimeout(() => {
      setCount(count + 1);
    }, 1000);

    return () => clearTimeout(delayDebounceFn);
  }, [streamSearchText]);

How can I do that?

Profer
  • 553
  • 8
  • 40
  • 81

3 Answers3

1

The main issue is that you have two useEffect calls, and so they're each handled, and the second triggers the first (after a delay), resulting in the duplication.

As I understand it, your goal is:

  1. Run videoGridState immediately on mount, and
  2. Run it again after a delay of 1000ms whenever streamSearchText changes

That turns out to be surprisingly awkward to do. I'd probably end up using a ref for it:

const firstRef = useRef(true);
const [streamSearchText, setStreamSearchText] = useState("");
useEffect(() => {
    if (firstRef.current) {
        // Mount
        videoGridState();
        firstRef.current = false;
    } else {
        // `streamSearchText` change
        const timer = setTimeout(() => {
            videoGridState();
        }, 1000);
        return () => clearTimeout(timer);
    }
}, [streamSearchText]);

Live Example:

const { useState, useRef, useEffect } = React;

function videoGridState() {
    console.log("videoGridState ran");
}

const Example = () => {
    const firstRef = useRef(true);
    const [streamSearchText, setStreamSearchText] = useState("");
    useEffect(() => {
        if (firstRef.current) {
            // Mount
            videoGridState();
            firstRef.current = false;
        } else {
            // `streamSearchText` change
            const timer = setTimeout(() => {
                videoGridState();
            }, 1000);
            return () => clearTimeout(timer);
        }
    }, [streamSearchText]);

    return <div>
        <label>
            Search text:{" "}
            <input
                type="text"
                value={streamSearchText}
                onChange={(e) => setStreamSearchText(e.currentTarget.value)}
            />
        </label>
    </div>;
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

You could also do the query immediately when streamSearchText is "", but that would happen every time streamSearchText was "", not just on mount. That may be good enough, depending on how rigorous you need to be.


Additionally, though, if you're still seeing something happen "on mount" twice, you may be running a development copy of the libraries with React.StrictMode around your app (the default in many scaffolding systems). See this question's answers for details on how React.StrictMode may mount your component more than once and throw in other seeming surprises.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Seems daunting way to do that... React should have some work around on this. isn't? since the class component does able to do this in easy way? – Profer Jun 16 '22 at 08:37
  • 1
    @Profer - I wouldn't call it *daunting*. :-) There are several things that are simpler with class components than function components -- and vice versa. You have to take the rough with the smooth, in both cases. – T.J. Crowder Jun 16 '22 at 08:39
  • It is going in both the condition not sure why.. – Profer Jun 16 '22 at 09:24
  • @Profer - I'm not sure what you mean by that. As you can see in the live example, the `videoGridState` function runs on mount and then a second after `streamSearchText` stops changing. – T.J. Crowder Jun 16 '22 at 09:44
  • Hi thanks for the answer... Actually I was using React.Strictmode that is why it is rendering my component couple of times. thanks a ton – Profer Jun 17 '22 at 06:43
  • Hi why we used `"useRef"` instead we can also use a simple `let` variable? – Profer Nov 25 '22 at 10:23
  • @Profer - I don't see how you can do this with "a simple `let` variable." Can you do an example? – T.J. Crowder Nov 27 '22 at 10:16
0

Your following useEffect() function makes this behaviour to happen:

  useEffect(() => {
    const delayDebounceFn = setTimeout(() => {
      setCount(count + 1);
    }, 1000);

    return () => clearTimeout(delayDebounceFn);
  }, [streamSearchText]);

Since it runs initially but called setCount() which updates the state, and forces a re-render of the component which in turn runs the first useEffect() since that has [count] in the dependency array.

And hence the cycle continues for the [count]

Imran Rafiq Rather
  • 7,677
  • 1
  • 16
  • 35
-1

const Example = () => {

const { useState, useRef, useEffect } = React;

// Any async function or function that returns a promise
function myDownloadAsyncFunction(data) {
console.log("222222222222222222222")
  return new Promise((resolve) => setTimeout(resolve, 1000));
}

function DownloadButton() {
  const [queue, setQueue] = useState(Promise.resolve());

  onClickDownload = () => {
setQueue(queue
  .then(() => myDownloadAsyncFunction('My data'))
  .catch((err) => {console.error(err)})
)
  }

  return (
<button onClick={onClickDownload()}>Download</button>
  );
}
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<Example />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Profer
  • 553
  • 8
  • 40
  • 81