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

Add initial value in useQueryParam hook #31

Closed
honeyspoon opened this issue Jul 29, 2019 · 12 comments
Closed

Add initial value in useQueryParam hook #31

honeyspoon opened this issue Jul 29, 2019 · 12 comments
Assignees

Comments

@honeyspoon
Copy link

We should be able to specify an initial value when using the useParam hook

could look like this

function comp(props) {
  const [foo, setFoo] = useQueryParam('foo', StringParam, 'hello')
  return <h1>{foo}</h1>
} 
@albinekb
Copy link

A potential workaround is by using language feature default:

const [foo = 'hello', setFoo] = useQueryParam('foo', StringParam)

@thexpand
Copy link

@hnspn That doesn't make sense. The custom hook is for query parameters. What you want is not a default value for the query parameter, but rather a fallback value for when there is no matching query parameter. So, I don't see a reason this should go as a functionality in the custom hook. What @albinekb suggested, is the only valid solution to what you're trying to achieve.

@honeyspoon
Copy link
Author

@thexpand you are 100% right.
I will use @albinekb's solution

@trebor
Copy link

trebor commented Jan 16, 2020

@albinekb's trick this solves SOME of the problem, but it doesn't actually set the value in the URL - thus i still need an if conditional to call setFoo() to have a default deep link. it seems like that third parameter would be an ideal way to specify a default.

p.s - i am not sure what happens when i comment in a closed issue, so might be be shouting into the wind. :)

@pbeshai pbeshai self-assigned this Apr 16, 2020
@Floriferous
Copy link

@albinekb's solution is nice, but it's only about the JS part. I want the state of this hook to be in sync with the URL, at all times, and here it would be out of sync. So why wouldn't it make sense to provide an initialiser?

I could do this to force the URL to be the same:

useEffect(() => {
  if (!foo) {
    setFoo(calculateInitialValue());
  }
}, [])

But this generates an unnecessary re-render of the component, which is a flaw.

@YassienW
Copy link

YassienW commented Mar 5, 2021

Same concern, i'd like to have the query parameter synced with the state instead of empty.

@tonymj76
Copy link

tonymj76 commented Mar 9, 2021

any update on this? really hoping there is a way to specify a default.

@Hengry
Copy link

Hengry commented Apr 20, 2021

I used @albinekb ' solution, but it make problems when the param is DateParam.

The initial value would change in every render time.

const [time = new Date(), setTime] = useQueryParam('time', DateQuery);
useEffect(() => {
  fetchByTime(time);
}, [time])

and I think the problem would also cause by Object and Array.

Though I can set the initial value in the hook, but it not make sense cause the initial value might be use in several hooks.

@iamyoki
Copy link

iamyoki commented Jul 15, 2021

If it's useQueryParams, then the default destructed array value won't work.

❌ Won't work. Because query will always receives object.

const [query={name: 'foo'}, setQuery] = useQueryParams({
  name: StringParam
})

✅ Let it work. Here is a little bit util function that combines the query with defaultQuery

/**
 * @template Q
 * @param {Q} query
 * @param {Q} defaultQuery
 */
export default function withDefaultQuery(query = {}, defaultQuery) {
  const formatedEntries = Object.entries(query)
    .map((v) => v[1] !== undefined && v)
    ?.filter((v) => v)

  return {
    ...defaultQuery,
    ...Object.fromEntries(formatedEntries),
  }
}

Use it

const [query, setQuery] = useQueryParams({
  name: StringParam
})
export default {query: withDefaultQuery(query, {name: 'foo'}), setQuery}

I hope that will help you guys 🍺 ~

@iamyoki
Copy link

iamyoki commented Jul 15, 2021

If it's useQueryParams, then the default destructed array value won't work.

❌ Won't work. Because query will always receives object.

const [query={name: 'foo'}, setQuery] = useQueryParams({
  name: StringParam
})

✅ Let it work. Here is a little bit util function that combines the query with defaultQuery

/**
 * @template Q
 * @param {Q} query
 * @param {Q} defaultQuery
 */
export default function withDefaultQuery(query = {}, defaultQuery) {
  const formatedEntries = Object.entries(query)
    .map((v) => v[1] !== undefined && v)
    ?.filter((v) => v)

  return {
    ...defaultQuery,
    ...Object.fromEntries(formatedEntries),
  }
}

Use it

const [query, setQuery] = useQueryParams({
  name: StringParam
})
export default {query: withDefaultQuery(query, {name: 'foo'}), setQuery}

I hope that will help you guys 🍺 ~

🎉 Again, I wanna share a setQuery util.
Some times we want to setQuery to undefined when the value implicitly converts to false.

Case:

// tabIndex = 0
setQuery({
 tabIndex: 0
})

In this case, the url will appears "?tabIndex=0", if we wanna hide it, we should set {tabIndex: undefined}.

Solution:

withSetUndefinedQuery.js

/**
 * @template P
 * @param {P} setQueryFn
 * @param {(key:any, value: any)=>any} filter
 * @return {P}
 */
export default function withSetUndefinedQuery(
  setQueryFn,
  filter = ([, value]) => value || undefined
) {
  return (props) => {
    setQueryFn(
      Object.fromEntries(
        Object.entries(props).map((kv) => [kv[0], filter([kv[0], kv[1]])])
      )
    )
  }
}

Use it

const [query, setQuery] = useQueryParams({
    tabIndex: NumberParam,
  })

export {query: withDefaultQuery(query), withSetUndefinedQuery(setQuery)}

Custom filter

const filter = ([key, value])=> key === 'tabIndex' && value === 0 ? undefined : value

const newSetQuery = withSetUndefinedQuery(setQueury, filter)

Here is my final usage

Xnip2021-07-15_11-04-56

@llezhava
Copy link

I also needed to provide initial value and wrote this wrapper. It also makes sure, that state is always into url.
It probably causes unnecessary rerender, but for my use case it is ok for now.

import { useEffect, useState, useRef } from 'react';
import {
  useQueryParams as _useQueryParams,
  QueryParamConfigMap,
  DecodedValueMap,
  SetQuery,
} from 'use-query-params';
import { merge } from 'lodash';

export const useQueryParams = <QPCMap extends QueryParamConfigMap>(
  config: QPCMap,
  initialValues: Partial<DecodedValueMap<QPCMap>>,
): [DecodedValueMap<QPCMap>, SetQuery<QPCMap>] => {
  const [init, setInit] = useState(false);
  const [query, setQuery] = _useQueryParams(config);

  const initialQueryValue = useRef({
    ...merge(initialValues, query),
  });

  useEffect(() => {
    if (init) return;
    if (!init) {
      setQuery(initialQueryValue.current, 'replaceIn');
    }
    setInit(true);
  }, [init, initialQueryValue, setQuery]);

  const result = init ? query : initialQueryValue;

  return [result, setQuery] as [DecodedValueMap<QPCMap>, SetQuery<QPCMap>];
};

export * from 'use-query-params';

@gandhis1
Copy link

gandhis1 commented Jul 6, 2022

Why is this closed? This still appears to be a valid feature ask.

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

No branches or pull requests