Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

withDefault() behavior when reset #531

Open
arwtyxouymz opened this issue Mar 27, 2024 · 10 comments
Open

withDefault() behavior when reset #531

arwtyxouymz opened this issue Mar 27, 2024 · 10 comments
Labels
feature New feature or request

Comments

@arwtyxouymz
Copy link

Context

What's your version of nuqs?

"nuqs": "^1.15.2"

Next.js information (obtained by running next info):


Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.3.0: Wed Dec 20 21:30:27 PST 2023; root:xnu-10002.81.5~7/RELEASE_ARM64_T8103
Binaries:
  Node: 18.16.0
  npm: 10.2.3
  Yarn: 1.22.21
  pnpm: 8.14.0
Relevant Packages:
  next: 13.5.6
  eslint-config-next: 13.5.6
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.3.3
Next.js Config:
  output: N/A

Are you using:

  • ✅ The app router
  • ❌ The pages router
  • ❌ The basePath option in your Next.js config
  • ❌ The experimental windowHistorySupport flag in your Next.js config

Description

Thank you for the great library. I love this library because it's really easy to use.
This is the kind of feature request.
When using withDefault, the default value is used when query value set to null.
But I think it's useful the library has the concept similar with initialState of useState.
The initial state is set at the initial mount and it can be clear with null.

Maybe something like this:

useQueryStates({
  q: parseAsString.withDefault('hoge', { resetToNull: true }),
})

How do you think?

@arwtyxouymz arwtyxouymz added the bug Something isn't working label Mar 27, 2024
@franky47 franky47 added feature New feature or request and removed bug Something isn't working labels Mar 27, 2024
@franky47
Copy link
Member

franky47 commented Mar 27, 2024

I'm not sure I follow your idea. In the case of useState, the initial state is used on mount and then lives on its own, but here the state is derived from the URL as the source of truth. The default value is only relevant when there is no query string with the given key in the URL, or it can't be parsed to a valid state value.

Setting the state to null (or to the default value when using clearOnDefault) is an indication to remove the query key from the URL, and from there the hook will behave as usual for a missing query key (return defaultValue ?? null).

Could you show me an example on how you would use such a feature?

@arwtyxouymz
Copy link
Author

arwtyxouymz commented Mar 28, 2024

@franky47 Thank you for your reply. I understood.

I use nuqs mainly to persist table filter state with tanstack/table and want to set initial filter which is resettable.
I was suffering from "resettable" with withDefault, but while writing this reply, I thought it would be good to simply set the URL in the sidebar, etc., with query parameters attached without using withDefault.

@matheins
Copy link

matheins commented Apr 3, 2024

I found this issue for the same reason described by @arwtyxouymz.
What makes withDefault a bit "unnatural", when you are coming from useState, is that the type of the value cannot be null

Example:

export const dateRanges = z.enum([
    "last_month",
    "month_before_last",
    "current_month",
  ]);

const [selectValue, setSelectValue] = useQueryState(
    "selected_value",
    parseAsStringEnum(dateRanges.options)
  );
  // const selectValue: "last_month" | "month_before_last" | "current_month" | null

  
const [selectValueWithDefault, setSelectValueWithDefault] = useQueryState(
   "selected_value_with_default",
    parseAsStringEnum(dateRanges.options).withDefault("last_month")
  );
  // const selectValueWithDefault: "last_month" | "month_before_last" | "current_month"

It would be awesome to have the option to set an initial value together with the option to set this value to null afterwards.

@franky47
Copy link
Member

franky47 commented Apr 4, 2024

What makes withDefault a bit "unnatural", when you are coming from useState, is that the type of the value cannot be null.

@matheins The default value differs from an initial state, it's what should be returned when the URL does not provide a valid state value. If you're ok with a null type to represent this absence of state in the URL, then you don't need to specify a default value. If however you need a non-nullable type at all times, then the default value is here to ensure that.

It would be awesome to have the option to set an initial value together with the option to set this value to null afterwards.

Could you show me a code example on how you'd use this?

@arwtyxouymz
Copy link
Author

@franky47

Could you show me a code example on how you'd use this?

Although my problem was resolved by adding query params to link href,
I thought it's useful if the parser has an option like .withInitilalValue().
This is the example usage of this option.

// Initially we can show filtered result to users
export const useStatusFilter = () => {
  const [statusFilterValue, setStatusFilterValue] = useQueryState(
    'status',
    parseAsStringEnum(['NotStarted', 'InProgress', 'Completed']).withInitialValue("NotStarted")
  );

  // filter can be cleared
  const clearFilter = () => setStatus(null);


  return {
    statusFilterValue,
    setStatusFilterValue,
    clearFilter
  }
}

@franky47
Copy link
Member

franky47 commented Apr 4, 2024

Although my problem was resolved by adding query params to link href

I think you came to the right conclusion by yourself. The reason an initial value like in useState does not work here is that the URL is the source of truth from which the state value will be derived. Introducing a branch from that source of truth by allowing an initial value potentially different than what the URL would provide makes the state harder to reason about IMHO.

@matheins
Copy link

matheins commented Apr 4, 2024

I like the idea of @arwtyxouymz. Using withInitialValue would be really comfortable.
When I got you right, @franky47, the workaround is setting the initial value via useSearchParams together with a useEffect OR I need to make sure every link, linking to this page, sets the search-param correctly.
Imo it would still make a lot of sense to abstract this, to make it more handy.

@cablate
Copy link

cablate commented Apr 17, 2024

"nuqs": "^1.17.0"
Hi, there's a similar problem with "withDefault()" option when the value is default actually.
(If this question is different from yours, I can start a separate issue post.)

const [tab, setTab] = useQueryState( 'tab', parseAsStringLiteral(TabType).withDefault('default') )

if the value of "tab" param in URL is not match the TabType, the value of tab will be set as 'default', but the tab query in URL is still the wrong one.
For example, /page?tab=wrong, the "wrong" is not match TabType, the tab state value will be set as 'default', and it's actually work.
As the same time, the URL was expected to become /page?tab=default, but there's nothing change.

I don't think nuqs has this feature, so i've tried use withOptions({clearOnDefault: true}) to avoid the wrong url displayed, but the tab param is still in URL.

@missile-developer
Copy link

missile-developer commented Jun 20, 2024

It seem to be similar to my use case

When I try to use parseAsIsoDateTime withDefault option, I can't clear the date since it will be fallback to the lastMonth and endOfToday. What I want it just remove these searchParams or at least set the startDate and endDate to null

Whether I use clearOnDefault it still be the same or maybe I use it the wrong. please correct me!

const [date, setDate] = useQueryStates(
{
	startDate: searchParams.startDate,
	endDate: searchParams.endDate,
	page: searchParams.page,
},
{
	startTransition,
	shallow: false,
	clearOnDefault: true,
}
)
	
const searchParams = {
 startDate: parseAsIsoDateTime.withDefault(lastMonth),
 endDate: parseAsIsoDateTime.withDefault(endOfToday),
}

For more information, they are using with nuqs/server. And for now, I overwrite this pattern by remove withDefault and use the default value with useEffect on the client mount instead.

const [date, setDate] = useQueryStates(
{
	startDate: searchParams.startDate,
	endDate: searchParams.endDate,
	page: searchParams.page,
},
{
	startTransition,
	shallow: false,
}
)
	
const searchParams = {
 startDate: parseAsIsoDateTime,
 endDate: parseAsIsoDateTime
}

// biome-ignore lint/correctness/useExhaustiveDependencies: Set effect on mount since we only want to set the initial date
	React.useEffect(() => {
		setDate({
			startDate: lastMonth,
			endDate: endOfToday(),
		})
	}, [])

@Duckinm
Copy link

Duckinm commented Aug 9, 2024

Will this be implement in the future or any alternative ways? I do agree that we may need a way to clear/set the date to undefined or null whether we have default as today date or someelse

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants