1

I'm currently working on a React project that utilizes React Router with react-router-dom. In one of my components, I'm using the setSearchParams function from useSearchParams to manage and modify URL query parameters. According to my understanding, when I use setSearchParams to update the URL query parameters and return the current object reference, it should not trigger the useEffect. However, I've noticed that the useEffect is triggered even when I return the same object reference.

Why does it happen?

I have an input field that triggers an useEffect whenever the user types. While I want the input to be responsive, I also want to avoid triggering the useEffect too frequently, especially when the user is typing rapidly.

How can I accomplish that?

Here's my code:

import {
  BrowserRouter as Router,
  Route,
  Routes,
  useSearchParams,
} from 'react-router-dom';
import React, { useEffect, useRef } from 'react';
import './style.css';

const Home = () => {
  const debounce = useDebounce();

  const [searchParams, setSearchParams] = useSearchParams();

  const name = searchParams.get('name') || '';

  useEffect(() => {
    console.log('useEffect triggered');
    // i am gonna call an API here
  }, [searchParams]);

  return (
    <div>
      <div>
        <label>Name:</label>
        <input
          type="text"
          value={name}
          onChange={(e) => {
            setSearchParams(
              (prev) => {
                prev.set('name', e.target.value);
                return prev;
              },
              { replace: true }
            );
          }}
        />
      </div>
    </div>
  );
};

function useDebounce() {
  const timeout = useRef(null);
  function debounce(cb, delay = 1000) {
    return (...args) => {
      if (timeout.current) clearTimeout(timeout.current);
      timeout.current = setTimeout(() => {
        cb(...args);
      }, delay);
    };
  }
  return debounce;
}
function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <a href="/">Home</a>
            </li>
          </ul>
        </nav>
        <Routes>
          <Route path="/" element={<Home />} />
        </Routes>
      </div>
    </Router>
  );
}

export default App;
5
  • That's exactly how useSearchParams works, you update the params, you get a new params reference on the next render cycle. You can't debounce React hooks. What exactly is it that is being called too often? ... you should debounce that function. Commented Sep 21, 2023 at 15:13
  • If I use the debounce function to prevent the effect from triggering too frequently, the input is going to feel unresponsive. Commented Sep 21, 2023 at 15:19
  • mutating prev value is a bad practice. if the component re-renders because of setSearchParams only a few times, it is completely okay. you don't need to worry about it. but if it re-renders it TOO much, there is definitely something wrong and you have to fix it (not hack it) Commented Sep 21, 2023 at 15:20
  • Why would the input "feel unresponsive" if the effect has nothing to do with it? You are not trying to debounce the input, you want to debounce the function that is called too many times. So far it isn't not clear what that function is though. Commented Sep 21, 2023 at 15:20
  • @Arashjahanbakhshan That is how the function update form of setSearchParams is intended to be used. It's not considered a "mutation". Commented Sep 21, 2023 at 15:22

1 Answer 1

0

According to my understanding, when I use setSearchParams to update the URL query parameters and return the current object reference, it should not trigger the useEffect. However, I've noticed that the useEffect is triggered even when I return the same object reference.

Why does it happen?

I think conceptually your understanding is "correct", but the current implementation of useSearchParams doesn't return a stable searchParams object reference. There is an open GitHub issue here regarding "Make setSearchParams stable".

Some workarounds are suggested in the thread.

What this means is that using searchParams as a useEffect hook dependency will trigger the effect each render cycle.

I have an input field that triggers an useEffect whenever the user types. While I want the input to be responsive, I also want to avoid triggering the useEffect too frequently, especially when the user is typing rapidly.

How can I accomplish that?

You can't debounce React hooks. You should debounce the function that would be called too often as a side-effect.

Example debouncing a function that calls the API.

const Home = () => {
  const debounce = useDebounce();

  const [searchParams, setSearchParams] = useSearchParams();

  const name = searchParams.get("name") || "";

  // Create memoized, stable debounced callback reference
  const someApi = useCallback(debounce((name) => {
    console.log("callin some API", { name });
  }), [])

  useEffect(() => {
    console.log("useEffect triggered", { name });
    // i am gonna call an API here
    someApi(name);
  }, [someApi, name]);

  return (
    <div>
      <div>
        <label>Name:</label>
        <input
          type="text"
          value={name}
          onChange={(e) => {
            setSearchParams(
              (prev) => {
                prev.set("name", e.target.value);
                return prev;
              },
              { replace: true }
            );
          }}
        />
      </div>
    </div>
  );
};

Edit how-can-i-debounce-user-input-for-responsive-yet-efficient-useeffect-triggering

enter image description here

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.