Search code examples
reactjshtml5-history

how to trigger a react component re-render after changing query param (without react-router)


In the react component below I want to display the current value of a url query param after updating it. Something like this is straightforward to do with react-router, but I am trying to implement manually in a more minimal app. The code below effectively updates the url, but does not trigger a re-render, so the displayed value never updates.

export default function ExampleComponent() {
    const params = new URLSearchParams(location.search);
    const randomValue = params.get("random");

    function updateRandomValue() {
        const updatedValue = Math.random().toString();
        const newURL = new URL(location.href);
        newURL.searchParams.set("random", updatedValue);
        history.pushState("", "", newURL);
    }

    return (
        <>
            <p>random value is {randomValue || "none"}</p>
            <button onClick={updateRandomValue}>update new random value</button>
        </>
    );
}

Am I handling the navigation wrong? Does there need to be a useEffect involved somehow? Is this functionality impossible without an outer component and/or some use of react state?


Solution

  • Figured it out after a little more noodling. Still need to get a better handle on the why, but changing the click handler from my original snippet to below resolves the bug, without needing additional state:

    function updateRandomValue() {
        const updatedValue = Math.random().toString();
        params.set("random", updatedValue);
        location.search = params.toString();
    }
    

    EDIT: Revisited this today and see it is wrong. It works but is forcing a browser refresh which is not what I want.

    Another EDIT:

    Not sure if this is considered a hack, but it accomplishes the ask and makes sense - duh, react components only rerender from state changes. In real life, the query param would most likely be used to change some other state, so this hack wouldn't be needed.

    import {useState} from "react";
    
    export default function ExampleComponent() {
        const [, setForceUpdate] = useState({});
    
        const urlParams = new URLSearchParams(location.search);
        const randomValue = urlParams.get("random");
    
        function updateRandomValue() {
            const updatedValue = Math.random().toString();
            const newURL = new URL(location.href);
            newURL.searchParams.set("random", updatedValue);
            history.pushState("", "", newURL);
            setForceUpdate({});
        }
    
        return (
            <div>
                <p>Random value from URL: {randomValue}</p>
                <button onClick={updateRandomValue}>Update Random Value</button>
            </div>
        );
    }