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