useEffect calling api's couple of times reactjs

Viewed 85

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?

2 Answers

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.

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]

Related