useSearchParam() sometimes doesn't get param when set in NextResponse.redirect()
What is the current behavior?
useSearchParam('redirect') from react-use returns null when redirected via next.js middleware after a router.push() navigation, despite the redirect parameter being present in the URL.
Steps to reproduce it and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn't have extra dependencies other than react-use. Paste the link to your JSFiddle or CodeSandbox example below:
- Set up a Next.js application with protected routes and authentication middleware.
- Create a form submission handler that uses
router.pushto navigate to a protected route:
const params = new URLSearchParams()
params.append('value', value.trim())
router.push(`/protected?${params.toString()}`)
- Implement middleware that redirects to login for protected routes:
// src/middleware.ts
if (isProtectedPath && !accessToken) {
const loginUrl = new URL('/auth/login', request.url)
// Add the current path as a redirect parameter
loginUrl.searchParams.set('redirect', `${request.nextUrl.pathname}?${request.nextUrl.searchParams}`)
return NextResponse.redirect(loginUrl)
}
- On the login page, attempt to access the redirect parameter using
useSearchParamand observer thatuseSearchParam('redirect')returns null.
Note that this does not happen when pasting the URL directly into the address bar or clicking an anchor tag.
What is the expected behavior?
useSearchParam('redirect') should return the redirect parameter value from the URL, as useSearchParams.get('redirect') from next/navigation does.
A little about versions:
- macOS 15.2 24C101 arm64
- Google Chrome Version 133.0.6905.0 (Official Build) dev (arm64)
-
react18.3.1 -
react-use17.5.1
Did this worked in the previous package version?
🤷
I spent some time wondering why my redirect from my login page wasn't working and ended up testing with next/navigation for sanity and found I was sane.
@yoonthegoon Just curious, can you try using useSearchParam together with the useLocation hook?
Specifically, in the component where you're calling useSearchParam, try calling useLocation() before it, even if you're not using the returned value.
Something like this:
useLocation();
const redirectParamReactUse = useSearchParam('redirect');
Does this setup give you the correct parameter value in your React component?
@yoonthegoon Just curious, can you try using
useSearchParamtogether with theuseLocationhook?Specifically, in the component where you're calling
useSearchParam, try callinguseLocation()before it, even if you're not using the returned value.Something like this:
useLocation(); const redirectParamReactUse = useSearchParam('redirect'); Does this setup give you the correct parameter value in your React component?
I looked into the source code for useSearchParam (from [react-use](https://github.com/streamich/react-use/blob/HEAD/docs/useSearchParam.md)), and it seems like calling useLocation() before useSearchParam() can help things work correctly.
In the docs, they show usage like this:
import { useSearchParam } from 'react-use';
const Demo = () => {
const edit = useSearchParam('edit');
return (
<div>
<div>edit: {edit || '🤷♂️'}</div>
<button onClick={() => history.pushState({}, '', location.pathname + '?edit=123')}>
Edit post 123
</button>
<button onClick={() => history.pushState({}, '', location.pathname + '?edit=999')}>
Edit post 999
</button>
<button onClick={() => history.pushState({}, '', location.pathname)}>
Close modal
</button>
</div>
);
};
But here’s the issue: history.pushState doesn't automatically trigger the 'pushstate' event, which means useSearchParam won’t update unless something listens for it.
In the useLocation implementation, they manually patch the pushState and replaceState methods like this:
const patchHistoryMethod = (method) => {
const history = window.history;
const original = history[method];
history[method] = function (state) {
const result = original.apply(this, arguments);
const event = new Event(method.toLowerCase());
event.state = state;
window.dispatchEvent(event);
return result;
};
};
So when you call useLocation() first. Even if you don’t use the return value it ensures these events are triggered properly, and useSearchParam can react to them.
That’s why I think calling useLocation() before useSearchParam() might solve your issue.
TL;DR: The docs don’t mention it, but for useSearchParam to reliably detect updates from pushState, you may need to use useLocation() beforehand so the patching happens. Might be worth improving the docs to make that clearer.