skip to Main Content

I have two useState state values currently, and they rely on each other. Here’s some pseudocode:

[getId, setId] = useState();
[getSearchParam1, setSearchParam1] = useSearchParams();

useEffect(() => {
  setId(getSearchParam1)
}, [getSearchParam1])

useEffect(() => {
  setSearchParam1(getId)
}, [getId])

where searchParam1 is just "www.google.com?searchParam1=xyz". The idea is that the searchParam and the id should always be the same value, but searchParam is a nullable value and id is either set to '' or an actual id. This code does work most of the time fine, but when I hit the back button on my browser, because searchParam changes, for some reason the useEffect hooks are activated simultaneously and constantly try to update each other’s values. This causes the useEffects to constantly fire, reloading the page forever until I refresh. Is there any way to avoid this from happening?

So far I’ve tried to set an isLoading state to prematurely break out of the useEffects, but that doesn’t seem like the best way to resolve the issue. There’s probably a better way I’m not thinking of.

The recommendation is to always have exhaustive hooks, so I don’t want to just remove the dependencies in the useEffect, but I’m not sure what else to do.

2

Answers


  1. Your best bet is to combine both effects and compare the values:

    Note: Convention-wise, the first value in the useState destructuring call is not a getter, it’s a value, so the "get" prefix is confusing and incorrect.

    const [searchParams, setSearchParams] = useSearchParams();
    const [id, setId] = useState();
    
    useEffect(() => {
      const searchParam1 = searchParams.get('searchParam1');
    
      if (id !== searchParam1) {
        setSearchParams(params => {
          params.set('searchParam1', id);
          return params;
        });
      }
    
      if (searchParam1 !== id) {
        setId(searchParam1);
      }
    }, [id, searchParams]);
    

    But a better way to do this would be to eliminate the id state and just stick with the searchParam1 param from useSearchParams.

    Login or Signup to reply.
  2. It is a bit of a React anti-pattern to duplicate passed state/props values into local state. In this case the searchParams value is the actual source of truth, there’s no need to over-complicate things by duplicating the search params values into local state and need to add a bunch of additional logic to synchronize the local state to the search params and search params to the local state. Just use the searchParams directly.

    const [searchParams, setSearchParams] = useSearchParams();
    
    const searchParam1 = searchParams.get("searchParam1") ?? "";
    

    There’s also a need to sometimes set the id based on other inputs
    which would then update the search parameters. Like a button to update
    id which would then consequently update search params.

    Use a callback handler to update the search params directly instead of messing the duplicate state and trying to synchronize. This effects a navigation change to the current route path but with updated search string, and the component will rerender and pick up the updated "searchParam1" value.

    Example:

    const handler = id => {
      setSearchParams(searchParams => {
        searchParams.set("searchParam1", id);
        return searchParams;
      });
    };
    
    <button type="button" onClick={() => handler("abc")}>
      Update Id
    </button>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search