Skip to content
Advertisement

React infinite recursion on history.push and useEffect

I’m a little new to react and I seem to have a chicken and egg problem. I want to have a query parameter in the the browser URL be synchronized with a useState variable in the page. The problem I have is that when I have useEffect do a history.push then react updates the variable which triggers another history.push and an infinite recursion happens. This is my attempt at a workaround but I have to have duplicate code. One copy is inside the useEffect and the other copy is outside the useEffect so I can use it without triggering the infinite recursion.

I thought I read that I could memoize something but I’m not sure about that. How can I avoid having this duplicate code?

import {useState, useEffect} from "react";
import {useHistory, useLocation} from "react-router-dom";

function useQuery() {
  return new URLSearchParams(useLocation().search);
}

export default function Home(props) {
  const history = useHistory();
  const query = useQuery();
  const [something, setSomething] = useState('');
  const [term, setTerm] = useState(query.get('term') === null ? '' : query.get('term'));
  const [urlParams, setUrlParams] = useState(duplicateCode());

  // this duplication of the code below
  function duplicateCode() {
    const params = new URLSearchParams();
    if ( term !== '' ) params.append('term', term);
    return params;
  }

  // this duplication of the code above
  useEffect(() => {
    const params = new URLSearchParams();
    if ( term !== '' ) params.append('term', term);
    setUrlParams(params);
  },[term]);

  // separate useEffect for term and history.push else get infinite recursion
  useEffect(() => {
    history.push('?' + urlParams);
    // simulate API call
    setSomething(urlParams.toString().replace("term=", ""));
  },[history, urlParams]);

  function handleSubmit(event) {
    setTerm(''+Math.random()*100);
    event.preventDefault();
  }
  return (
    <div>
    <p>Term = {something}</p>
    <form onSubmit={handleSubmit}><button>Button</button></form>
    </div>
  );
}

And App.js just in case.

import {React}  from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";

import Home from "./Home";

export default function App(props) {
  return (
    <section className="App">
    <Router>
      <Route exact path="/" component={Home} />
    </Router>
    </section>
  );
};

Advertisement

Answer

You could have the useEffect on the props and parse there the query string. The react router will send default props to the component named location and matches (you do not have anything complex in the path, so no matches)

Code:

import { React } from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import queryString from 'query-string'
import { useState, useEffect} from "react";
import { useHistory } from "react-router-dom";

function Home(props) {
  const history = useHistory();
  const [term, setTerm] = useState(props.termIn === null ? "" : props.termIn);

  useEffect(() => {
    const params = queryString.parse(props.location.search);
    if (params.term) {
      setTerm(params.term);
    }
  }, [props]);

  function handleSubmit(event) {
    history.push("/?term=" + (Math.random() * 100));
    event.preventDefault();
  }
  return (
    <div>
      <p>
        Term = {term}
      </p>
      <form onSubmit={handleSubmit}>
        <button>Button</button>
      </form>
    </div>
  );
}
export default function App(props) {
  return (
    <section className="App">
      <Router>
        <Route
          path="/"
          component={Home}
        />
      </Router>
    </section>
  );
}

Code sandbox link: https://codesandbox.io/s/so-67231900-hm5od

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement