use-query-params
use-query-params copied to clipboard
can this works with next.js ?
hi, can this lib works with next.js ?
i got this error on page when refresh (ssr mode) ....
TypeError: Cannot read property 'search' of undefined
Hmm, I suppose this is due to window.location
not being a thing. I've never used next.js though, would you be able to reproduce this in an example repo I could use to debug?
I've ran into similar issue with Gatsby: error Building static HTML failed for path "/" (...) WebpackError: TypeError: Cannot read property 'search' of undefined
I suppose that this issue for @reach/router could point us in the right direction with preventing this from happening.
I've added PR #25 that should enable use-query-params
to work with Gatsby/NextJS.
Just wanted to add a +1 to this issue. It would be nice to use this with SSR applications and @zielinsm seems to have a working fix ready.
EDIT: I tested out PR #25 locally and found it to be non-functional. I'll look into submitting an alternate PR.
Would also love to usue this SSR (nextjs in particular), have you made any progress @nickhavenly ?
@nickhavenly, I have made some changes to the PR I submitted after finding out I wasn't returning a value adjusted to the environment. This should be working now. I've re-requested a review from @pbeshai.
From what I understand so far, it seems to me that this would allow it to just perhaps pass/build successfully. I am wondering if @nickhavenly actually wanted to look into building something like currently supported ReactRouter / ReachRouter providers, i.e. NextRouterProvider?
For example, if I click on a "filter" component and update a value, I would be getting a new request on the next SSR server with new value of the filter, so I can fetch different data on the servers getInitilProps? I am also first time working with nextjs so I am not 100% sure how involved that is.
@gcloeval @zielinsm,
I was able to get it working in a similar fashion to PR #25 but after getting to that point realized that I needed to integrate with NextRouter for my use case. My deadline was too tight to spend time on this so I ended up rolling my own rudimentary solution which interfaced directly with next router.
For non-package specific SSR @zielinsm's PR should be sufficient but I think the correct long term solution here is to integrate a NextRouterProvider as @gcloeval suggested. Unfortunately I'm still pushing against tight deadlines and don't have the extra bandwidth to pursue this as an option at the moment. Hopefully someone else can carry the torch as this is a really neat package and I'd love to refactor for it in the future.
@nickhavenly - yeah same here, bulding a proper NextRouterProvider seems a bit too over my head at the moment. Do you mind sharing your rudimentary soltion, or at least some snippets what you ended up using - maybe a) I can also use it for my app and b) it can be a good starting point if somebody wants to pick it up here?
/**
* Next.js query param provider.
*/
const QueryProvider: React.FC = ({ children }) => {
const router = useRouter()
// eslint-disable-next-line no-shadow
const history = {
push: ({ search }: Location) =>
router.push({ search, pathname: router.pathname }),
replace: ({ search }: Location) =>
router.replace({ search, pathname: router.pathname }),
}
// eslint-disable-next-line no-shadow
const location = {
search: router.asPath.replace(/[^?]+/u, ''),
} as Location
return (
<QueryParamProvider history={history} location={location}>
{children}
</QueryParamProvider>
)
}
If you use Reach router, see https://github.com/pbeshai/use-query-params/issues/51#issuecomment-546393827. It could help :)
Just wanted to add some small updates (useMemo, really) onto @lucasconstantino's work. (Thanks for doing the brunt of the job!)
// import
import React, {memo, useMemo} from 'react';
import pt from 'prop-types';
import {useRouter} from 'next/router';
import {QueryParamProvider as ContextProvider} from 'use-query-params';
// component
export function QueryParamProvider(props) {
const {children, ...rest} = props;
const router = useRouter();
const location = useMemo(() => (process.browser ? window.location : {
search: router.asPath.replace(/[^?]+/u, ''),
}), [router.asPath]);
const history = useMemo(() => ({
push: ({search}) => router.push({search, pathname: router.pathname}),
replace: ({search}) => router.replace({search, pathname: router.pathname}),
}), [router.pathname]);
return (
<ContextProvider {...rest} history={history} location={location}>
{children}
</ContextProvider>
);
}
QueryParamProvider.propTypes = {
children: pt.node,
};
// export
export default memo(QueryParamProvider);
Has anyone gotten the above provider to work with Next? I'm getting a Cannot read 'split' of undefined
when trying to use useQueryParam
.
Having the same issue above.
Cannot read 'split' of undefined
Currently implemented this (a typescript adapted version of @mikestopcontinues ) in my next.js and it's working like a charm.
Only thing I'm not really a fan of is how we are supposed to handle the "initial state" by having to set it manually in componentDidMount
but that's not a problem with the next.js/ssr it's part of the library logic itself.
the above split of undefined
problem are most probably due to the fact that the pages reads the query on load and if no query is found your state is indeed undefined, and you can go around that with something like this:
// create your defaultState variable
const defaultQuery = {
filters: {
tags: [],
category: undefined,
textSearch: undefined,
}
}
// on mount smart merge the existing query with your default values
useEffect(
() => {
setQuery({
...defaultQuery,
...(query.filters && { filters: { ...query.filters } }) // if a value is present in the first load query url, it overwrite the default values
}, 'replace'); // we want to replace because we don't want unexpected states, if you need something else feel free to change this
},
[]
)
If you use https://nextjs.org/docs/routing/dynamic-routes you should modify @mikestopcontinues example
import React, { memo, useMemo } from 'react';
import { useRouter } from 'next/router';
import { QueryParamProvider as ContextProvider, QueryParamContextValue } from 'use-query-params';
export const QueryParamProviderComponent = (props: { children?: React.ReactNode }) => {
const { children, ...rest } = props;
const router = useRouter();
const match = router.asPath.match(/[^?]+/);
const pathname = match ? match[0] : router.asPath;
const location = useMemo(
() =>
process.browser
? window.location
: ({
search: router.asPath.replace(/[^?]+/u, ''),
} as QueryParamContextValue['location']),
[router.asPath],
);
const history = useMemo(
() => ({
push: ({ search }: QueryParamContextValue['location']) =>
router.push(router.pathname, { search, pathname }, { shallow: true }),
replace: ({ search }: QueryParamContextValue['location']) =>
router.replace(router.pathname, { search, pathname }, { shallow: true }),
}),
[pathname, router],
);
return (
<ContextProvider {...rest} history={history} location={location}>
{children}
</ContextProvider>
);
};
export const QueryParamProvider = memo(QueryParamProviderComponent);
@baybara-pavel what should pathNameMatch
be in your example?
Actually, it looks like maybe just a check on match
?
@brycereynolds, yep you right, thank you, just my typo. Fixed my original comment.
@baybara-pavel thanks for this example. We ended up changing the push
/replace
functions to:
router.push(`${router.pathname}${search}`, { search, pathname }, { shallow: true });
// and
router.replace(`${router.pathname}${search}`, { search, pathname }, { shallow: true });
In order to avoid this bug in next.js's router when doing client-side navigation:
https://github.com/zeit/next.js/issues/9473
@matt-dutchie I used a different approach to solve the same issue.
router.push({ pathname: router.pathname, query: router.query }, { search, pathname }, { shallow: true });
// and
router.replace({ pathname: router.pathname, query: router.query }, { search, pathname }, { shallow: true });
@baybara-pavel Thanks for your example! This is great!
I had to change it slightly, since QueryParamContextValue
doesn't seem to be exported anymore:
import React, { memo, useMemo } from 'react';
import { useRouter } from 'next/router';
import { QueryParamProvider as ContextProvider } from 'use-query-params';
export const QueryParamProviderComponent = (props: {
children?: React.ReactNode;
}) => {
const { children, ...rest } = props;
const router = useRouter();
const match = router.asPath.match(/[^?]+/);
const pathname = match ? match[0] : router.asPath;
const location = useMemo(
() =>
process.browser
? window.location
: ({
search: router.asPath.replace(/[^?]+/u, ''),
} as Location),
[router.asPath]
);
const history = useMemo(
() => ({
push: ({ search }: Location) =>
router.push(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true }
),
replace: ({ search }: Location) =>
router.replace(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true }
),
}),
[pathname, router]
);
return (
<ContextProvider {...rest} history={history} location={location}>
{children}
</ContextProvider>
);
};
export const QueryParamProvider = memo(QueryParamProviderComponent);
Also included the changes as proposed by @STEVEOO6, thanks for that, too!
@Regaddi thanks for your solution, it worked for me ❤️
Just a few things my typescript was complaining about:
- should use
u
flag. I'm not good at regexp, so not sure if it's necessary.
const match = router.asPath.match(/[^?]+/);
-
push
andreplace
return promises and should useasync
&await
() => ({
push: ({ search }: Location) =>
router.push(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true }
),
replace: ({ search }: Location) =>
router.replace(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true }
),
}),
[pathname, router]
);
Was having one small problem with @Regaddi 's solution (which, this aside, works perfectly): history itself needs to also export location, so:
const history = useMemo(
() => ({
push: ({ search }: Location) =>
router.push(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true }
),
replace: ({ search }: Location) => {
router.replace(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true }
);
},
location,
}),
[pathname, router.pathname, router.query, location.pathname]
);
without this, LocationProvider.js would be using its most recently stored locationRef rather than the present location in the browser, which causes it to use an outdated location if you're updating the query params as you're moving away from a page (basically in the hooks version of "componentWillUnmount").
Here's a complete example code based on all the previous answers:
import React, { memo, useMemo } from 'react'
import { useRouter } from 'next/router'
import { QueryParamProvider as ContextProvider } from 'use-query-params'
export const QueryParamProviderComponent = (props: {
children?: React.ReactNode
}) => {
const { children, ...rest } = props
const router = useRouter()
const match = router.asPath.match(/[^?]+/)
const pathname = match ? match[0] : router.asPath
const location = useMemo(
() =>
typeof window !== 'undefined'
? window.location
: ({
search: router.asPath.replace(/[^?]+/u, ''),
} as Location),
[router.asPath]
)
const history = useMemo(
() => ({
push: ({ search }: Location) =>
router.push(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true }
),
replace: ({ search }: Location) => {
router.replace(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true }
)
},
location,
}),
[pathname, router.pathname, router.query, location.pathname]
)
return (
<ContextProvider {...rest} history={history} location={location}>
{children}
</ContextProvider>
)
}
export const QueryParamProvider = memo(QueryParamProviderComponent)
Also replaced process.browser
check with typeof window !== 'undefined'
which is the right way of doing this (officially recommended).
Edition for Next 10.1 https://nextjs.org/blog/next-10-1#router-methods-scroll-to-top. Disable auto scroll to top on each query-string changing.
import React, { memo, useMemo } from 'react'
import { useRouter } from 'next/router'
import { QueryParamProvider as ContextProvider } from 'use-query-params'
export const QueryParamProviderComponent = (props: {
children?: React.ReactNode
}) => {
const { children, ...rest } = props
const router = useRouter()
const match = router.asPath.match(/[^?]+/)
const pathname = match ? match[0] : router.asPath
const location = useMemo(
() =>
typeof window !== 'undefined'
? window.location
: ({
search: router.asPath.replace(/[^?]+/u, ''),
} as Location),
[router.asPath]
)
const history = useMemo(
() => ({
push: ({ search }: Location) =>
router.push(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true, scroll: false }
),
replace: ({ search }: Location) => {
router.replace(
{ pathname: router.pathname, query: router.query },
{ search, pathname },
{ shallow: true, scroll: false }
)
},
location,
}),
[pathname, router.pathname, router.query, location.pathname]
)
return (
<ContextProvider {...rest} history={history} location={location}>
{children}
</ContextProvider>
)
}
export const QueryParamProvider = memo(QueryParamProviderComponent)
@baybara-pavel Dependency array on history
object should be [pathname, location, router]
.
I've published an adapter for Next.js: next-query-params
It was originally based on the code that is discussed in this issue but also contains a few bugfixes that popped up over time as well as support for the App Router. Thanks everyone for the collaboration here!
@pbeshai any comment on including this in the package? Would be neat to have built-in nextjs support. I don't think you would even have to maintain it, I'm sure people would be happy to help out with updating it/ensuring that it works.
A note for the people following along here: I've just published [email protected]
which is based on use-query-params@2
and the introduced concept of adapters.
This removes the necessity for a wrapper and integrates much better now, including being able to make use of use-query-params
directly.
If the adapter is working well, you're certainly welcome to PR it into this repo if you'd like. Thanks for taking the initiative on this work! 🙇