react-router icon indicating copy to clipboard operation
react-router copied to clipboard

[Bug]: useSearchParams not giving correct value with useBlocker

Open abskrj opened this issue 1 year ago • 1 comments

What version of React Router are you using?

6.25.1

Steps to Reproduce

  1. create a Navigation Blocker Hook (which blocks query params change as well)
import { useEffect, useCallback } from 'react';
import { useLocation, useBeforeUnload, useBlocker } from 'react-router-dom';
import { validate as validateUUID } from 'uuid';

const useNavigationBlocker = (
    shouldBlock: boolean,
    checkSearchParams?: boolean,
) => {
    const location = useLocation();

    const blocker = useBlocker(
        useCallback(
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            (tx: any) => {
                if (!shouldBlock) return false;

                const nextLocation = tx.nextLocation;
                const currentPathname = location.pathname;
                const nextPathname = nextLocation.pathname;

                const currPathNameArr = currentPathname
                    .split('/')
                    .filter(Boolean);
                const nextPathNameArr = nextPathname.split('/').filter(Boolean);

                // Check for search params changes
                if (checkSearchParams) {
                    const currentSearchParams = new URLSearchParams(
                        location.search,
                    );
                    const nextSearchParams = new URLSearchParams(
                        nextLocation.search,
                    );

                    const searchParamsChanged =
                        Array.from(currentSearchParams.entries()).some(
                            ([key, value]) =>
                                nextSearchParams.get(key) !== value,
                        ) ||
                        Array.from(nextSearchParams.entries()).some(
                            ([key, value]) =>
                                currentSearchParams.get(key) !== value,
                        );

                    if (searchParamsChanged) {
                        return true; // Block navigation if search params have changed
                    }
                }

                // Allow navigation if saving a new item and redirecting to the item details page
                if (
                    currPathNameArr.length === 2 &&
                    nextPathNameArr.length === 2 &&
                    currPathNameArr[0] === nextPathNameArr[0] &&
                    currPathNameArr[1] === 'new' &&
                    validateUUID(nextPathNameArr[1])
                ) {
                    return false;
                }

                // Allow navigation if only search params are different
                if (currentPathname === nextPathname) {
                    return false;
                }

                return true;
            },
            [shouldBlock, location, checkSearchParams],
        ),
    );

    useBeforeUnload(
        useCallback(
            (event) => {
                if (shouldBlock) {
                    event.preventDefault();
                    event.returnValue = '';
                }
            },
            [shouldBlock],
        ),
    );

    useEffect(() => {
        if (blocker.state === 'blocked') {
            const proceed = window.confirm(
                'You have unsaved changes. Are you sure you want to leave?',
            );
            if (proceed) {
                setTimeout(blocker.proceed, 0);
            } else {
                blocker.reset();
            }
        }
    }, [blocker]);

    return blocker.state === 'blocked';
};
export default useNavigationBlocker;
  1. use this Hook in your Component
  2. fetch value of query using useSearchParam
  3. update the query params on some button click
  4. cancel the navigation change confirmation
  5. you will get the new value from the query param

Expected Behavior

after cancelling the navigation prompt you are blocking the navigation, though it doesn't reflect on the query in url, it updates in the state (searchParam)

Actual Behavior

it should be returning the old value in searchParam, after navigation is blocked

abskrj avatar Oct 25 '24 08:10 abskrj

Could you please create an interactive example on codesandbox or similar?

OlegDev1 avatar Oct 25 '24 14:10 OlegDev1

This issue has been automatically closed because we haven't received a response from the original author 🙈. This automation helps keep the issue tracker clean from issues that aren't actionable. Please reach out if you have more information for us! 🙂

github-actions[bot] avatar Nov 04 '24 22:11 github-actions[bot]

I was able to repro the bug. This is a stackblitz with an environment that allows you to repro. I used 6.27.0

https://stackblitz.com/edit/github-ypgwe9?file=src%2Fapp.tsx,src%2Fmain.tsx

I've attached two videos explaining the bug as well:

  1. (control) Expected behavior:

https://github.com/user-attachments/assets/0a4dc1b5-9472-4188-8428-4c8f2731e770

  1. Search params returning a value that should be blocked, IF using the (prev) value in the setSearchParams function

https://github.com/user-attachments/assets/4fff6c02-f1a1-4146-8d3e-a72cfe3be88d

Fattimo avatar Nov 09 '24 00:11 Fattimo

Opening another issue, as this issue is closed and may not have any active maintainers looking at it: https://github.com/remix-run/react-router/issues/12256

Fattimo avatar Nov 09 '24 00:11 Fattimo

@abskrj, I ran into the same issue, maybe this workaround will work for you: https://github.com/remix-run/react-router/issues/12256#issuecomment-2465946996

Fattimo avatar Nov 09 '24 00:11 Fattimo

Thanks @Fattimo, had written my own implementation using useLocation, will do a perf test with your solution and post it.

abskrj avatar Nov 09 '24 07:11 abskrj

This was fixed by https://github.com/remix-run/react-router/pull/12784 and will be available in the next release

brophdawg11 avatar Jun 30 '25 20:06 brophdawg11