Giter Club home page Giter Club logo

use-query-params's Introduction

useQueryParams

A React Hook, HOC, and Render Props solution for managing state in URL query parameters with easy serialization.

Works with React Router 5 and 6 out of the box. TypeScript supported.

npm Travis (.com)


When creating apps with easily shareable URLs, you often want to encode state as query parameters, but all query parameters must be encoded as strings. useQueryParams allows you to easily encode and decode data of any type as query parameters with smart memoization to prevent creating unnecessary duplicate objects. It uses serialize-query-params.

Docs

Packages

This is a monorepo managed with Lerna.

Package Version Docs Description
use-query-params npm use-query-params React library
serialize-query-params npm serialize-query-params js library

Development

To get running locally:

npm install
npx lerna bootstrap --hoist --scope "use-query-params" --scope "serialize-query-params"
npm build
npm test

Set up examples:

lerna bootstrap --scope "*-example"
lerna link

Then run one:

lerna run --scope react-router-example start

use-query-params's People

Contributors

andrewkvalheim avatar austin-payne avatar austinleegordon avatar bjoluc avatar chaosex avatar damianosipiuk avatar dartess avatar denis-sokolov avatar dependabot[bot] avatar dprgarner avatar frezc avatar guillaume avatar jeffprinty avatar jmcpeak avatar kajirikajiri avatar kuroski avatar lilac-ss avatar lisandro52 avatar miclle avatar othree avatar pbeshai avatar pocesar avatar shaddix avatar sibelius avatar waldyrious avatar willemarcel avatar yifei-zhan avatar yurtaev avatar zielinsm avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

use-query-params's Issues

useQueryParams initialization vs. setting the params

Hey :)

A quick question. In the useQueryParams example we have:

const [query, setQuery] = useQueryParams({
    x: NumberParam,
    q: StringParam,
    filters: ArrayParam,
  });
  const { x: num, q: searchQuery, filters = [] } = query;

setQuery(
            { x: Math.random(), filters: [...filters, 'foo'], q: 'bar' },
            'push'
          )

And the question is: why on init we have {q: StringParam} and on set {q: 'bar'} ... I mean in the first example the value is an object, in the second example is a string.

In all examples (best practices?) on https://reactjs.org/docs/hooks-overview.html a hook's init value and the set function values have the same structure.

Thank you.

PS.

I'm trying to set up useQueryParams inside an useEffect and the above behavior stops me achieve the result I want:

const [queryParams, setQueryParams] = useQueryParams({});

  useEffect(() => {
    if (queryParamsFromFilters !== undefined) {
      setQueryParams(queryParamsFromFilters); // This causing an URL like http://localhost:3000/?location=%5Bobject%20Object%5D&q=%5Bobject%20Object%5D
    }
  }, [queryParamsFromFilters]);

Decode a query param again on config change

This will be very useful for cases when a config created dynamically, something like this:

const PageParam = useMemo(() => RangeParam.create(1, pagesCount), [pagesCount]);

const [page = 1, setPage] = useQueryParam('page', PageParam);

Here RangeParam is a custom QueryParamConfig which decodes a value only when it falls into the given range, otherwise undefined is returned and the default page (1) is used. Looks like config should be passed to the useMemo and useCallback dependencies in useQueryParam hook to achieve this behaviour.

Add initial value in useQueryParam hook

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>
} 

Concurrent updates fail with mocked and in-memory histories

This is the same problem that was raised in #54, but the fix in #61 only seems to work if the history object provided in context has a location property. However, some of the functions which create a history object from React and Reach router, like adaptWindowHistory and adaptReachHistory, explicitly don't put that location object on it. This is awkward because now we can write code that works in the browser but fails in tests.

Here's an example of a test that I can add to src/__tests__/useQueryParam-test.tsx that fails, but should be passing if the bug in #61 was fixed:

it('sets distinct params in the same render', () => {
  const { wrapper } = setupWrapper({
    foo: '1',
    bar: '1',
  });
  const { result, rerender } = renderHook(
    () => [
      useQueryParam('foo', NumberParam),
      useQueryParam('bar', NumberParam),
    ],
    { wrapper }
  );
  const [[foo1, setFoo], [bar1, setBar]] = result.current;
  expect([foo1, bar1]).toEqual([1, 1]);

  setFoo(2, 'replaceIn');
  setBar(2, 'replaceIn');
  rerender();

  const [[foo2], [bar2]] = result.current;
  expect([foo2, bar2]).toEqual([2, 2]); // Fails, instead receiving [1, 2]
});

I'm not sure yet if this can be reproduced in a browser.

great server side addition

if you could decouple the (de)serializing logic from React, and make it a dependency on this project, would be very easy to use on the server as well (as query-string works on node.js)

Change ArrayParam to use the standard repeated query param style

See this Reddit thread for context, but essentially people raised the point that query params support arrays directly and it's weird to have a custom syntax as the default in a library. Despite my preference for the conciseness of the underscore syntax, I agree with them.

Currently, the following code works, but will have typing issues:

const MyArrayParam = {
  encode: val => val,
  decode: str => str,
};
const [arr, setArr] = useQueryParam('arr', MyArrayParam);

// ?arr=a&arr=b&arr=c produces arr=["a","b","c"]

setArr(["d", "e"])
// will produce ?arr=d&arr=e
  • Update types to work with returning not just strings but string arrays, this may involve removing the EncodedQuery type and using the ParsedQuery from query-string directly.
  • Either remove the current array params or rename them to DelimitedArrayParam.

how to read param (parsed) outside component?

What is the proper way to read param outside a component? or before rendering the component?

My use case is render as you fetch, where I fetch some data based on query string parameters

DelimitedArrayParam gets URIencoded

Here is an example.

The url bar in the preview window show the correct value for parameter x. In reality (you can check it by clicking the "Open in new window" button next to the url bar) the params are uri encoded resulting in ?x=1%7C2 instead of ?x=1|2.

"query-string" library doesn't do it with arrayFormat="comma". The only reason that I can see this happening is that the stringify function is called after the encoding happens so "query-string" treats it as a string and uri encodes it.

I'd assume the way to fix it is to set encode to false when calling the stringify. I looked around for possible settings that I can use on a higher level (as the "query-string" level is not accessible) but couldn't find anything.

Is there anything that can be done to prevent this from happening?

How to add dynamic query params?

I have a situation where I need to define some pre-decided params and then we have some fields we are querying the backend by that belong in a JSONField. So the params can be totally dynamic. Everything else in my app is set up in a way to support that but since we define the param types on init, it doesn't look like that would be supported. Is there some way I am not seeing?

The quick fix is just to manually define the ones I know about, but would love to add them on the fly.
const [query, setQuery] = useQueryParams({ game: StringParam, platforms: CommaArrayParam, platform_mics: CommaArrayParam, });

and would want to add something like json_field__foo: Comma Array Param as I need to query for it.

fix peerDep of react and react-dom

"peerDependencies": {
    "react": "^16.8.0",
    "react-dom": "^16.8.0"
  },

causes another react dep to be installed

moving to

"peerDependencies": {
    "react": ">=16.8.0",
    "react-dom": ">=16.8.0"
  },

fixes the issue

Suggestion: Add a `mapParamsToProps` to `withQueryParams`

Coming from React-Redux's connect it felt limiting that the props had to be called query and setQuery. Having a mapParamsToProps(query, setQuery, ownProps?) allows complex configurations without rewriting components to use query and setQuery.

If there is interest, I'm willing to write a proper PR, but I would like guidelines on how to do it? As a breaking change or as a second HOC?

I apologize for my mix of JS and TS, I wrote my withQueryParams in JSX, but wanted to use the example in the README:

import React from 'react';
import {
  useQueryParams,
  StringParam,
  NumberParam,
  ArrayParam,
} from 'use-query-params';

// withQueryParams.jsx
const withQueryParams = (paramConfigMap, mapParamsToProps) => (WrappedComponent) => {
  const Component = (props) => {
    const [query, setQuery] = useQueryParams(paramConfigMap);
    return <WrappedComponent {...mapParamsToProps(query, setQuery, props)} {...props} />;
  };
  Component.displayName = `withQueryParams(${WrappedComponent.displayName ||
    WrappedComponent.name ||
    'Component'})`;
  return Component;
};

// example.tsx
const WithQueryParamsExample = ({
  x: num,
  q: searchQuery,
  filters = [],
  onChange = () => {},
  onChangeAll = () => {},
}: any) => {
  return (
    <div>
      <h1>num is {num}</h1>
      <button onClick={onChange}>Change</button>
      <h1>searchQuery is {searchQuery}</h1>
      <h1>There are {filters.length} filters active.</h1>
      <button onClick={onChangeAll}>Change All</button>
    </div>
  );
};

const mapParamsToProps = (query, setQuery) => {
  const { x, q, filters } = query;
  return {
    x,
    q,
    filters,
    onChange: () => setQuery({ x: Math.random() }),
    onChangeAll: () =>
      setQuery({ x: Math.random(), filters: [...filters, 'foo'], q: 'bar' }, 'push'),
  };
};

export default withQueryParams(
  {
    x: NumberParam,
    q: StringParam,
    filters: ArrayParam,
  },
  mapParamsToProps,
)(WithQueryParamsExample);

does not work in IE 11

hi. thanks for nice hook!
I wonder why tsconfig target is "es2018"? are there any issues with transpiling to "es5"?
at the moment we have no IE support =(

Components wrapped in `React.memo()` do not update when the query params change

If useQueryParam() is called within a component wrapped in React.memo(), the memoized component doesn't re-render when the params are changed.

Repro:
https://codesandbox.io/s/params-test-p9yxh

Try clicking the first false (next to "Unmemoized value:"). The query param in the URL gets set to 1, and the unmemoized value updates to true, but the memoized value stays the same. Clicking on the memoized value can cause the unmemoized value to change, but the memoized value itself still won't change.

I had hoped to submit a PR for this, but I'm still not sure what's causing it. As far as I know, calls to useContext should effectively override component memoization -- see a similar demo here: https://codesandbox.io/s/memoized-context-test-m4xpt

Edit: Here's a PR with a failing test, at least: #48

DateParam with moment.js

Good morning!
Is there a way to use DateParam with moment.js? It doesn't recognize as a Date object.

Screenshot from 2019-11-14 17-43-07

Thank you for your time!

Set empty query param (i.e. ?foo)

When I call setFoo(null) the query param is removed. Is it possible to set an empty value instead? Either ?foo&bar=value or ?foo=&bar=value
I could create an EmptyParam encoder/decoder, is there any simpler option?

Params not reset on route change when using React Router's HashRouter

Search params are not reset on navigation when using React Router's HashRouter.

react-router: 5.0.0
react-router-dom: 5.0.0
use-query-params: 0.3.2

Reproduced here: https://codesandbox.io/s/angry-microservice-velbw

  1. Click "to bar" button. Observe that the preview Browser tab URL updates to https://qtqcr.codesandbox.io/?foo=bar.
  2. Click "Link two" link. Preview URL is now https://qtqcr.codesandbox.io/?foo=bar#/two, when it should be https://qtqcr.codesandbox.io/#/two.

If you comment out the App on line 51, which doesn't use HashRouter, the params in the URL update as expected.

(Edited with correct link)

Value of useQueryParam not updated after firing setter without third-party Router

Thanks for your work on this library!

I'm using useQueryParam to get and set a value, e.g.:

const [foo, setFoo] = useQueryParam('foo', NumberParam)

…but when I fire the setter, the value is updated only in the query string and not in the value returned from the hook. Am I missing something? I swear I see examples in the repo doing this very same thing and working… 😐

EDIT: The only thing I'm doing differently from the docs, if I'm reading things right, is that I'm not passing in anything about a routing library since I'm not using one in my app. Based on looking over the QueryParamProvider component source code, it looks like providing these props is optional?

Working example here — clicking the button will set 'foo' to a random integer, but the value (which is rendered in the DOM) is only updated on refreshing the page: https://codesandbox.io/embed/cool-night-mv67g

EDIT 2: Just to see what happens, I've tried installing React Router and wrapping my app with BrowserRouter, passing the Route to QueryParamProvider. Things now work, but I have no other need for React Router (or routing of any kind at the moment). Is it not possible to use this lib without a third party router? Thanks!

Specify query param configurations in one place

The current API requires that you specify the structure of the query parameters in every component that wants to access params. Consider a large app with 1000 components that all need x, q, and filters. You will see the following code 1000 times:

const [query, setQuery] = useQueryParams({
    x: NumberParam,
    q: StringParam,
    filters: ArrayParam,
  });

This seems unnecessarily verbose since it is not like x will be a NumberParam in one component, and an ArrayParam in another.

Have you considered DRY'ing up the API by allowing this to be specified in a single location:

<QueryParamProvider params={{
    x: NumberParam,
    q: StringParam,
    filters: ArrayParam,
  }}>
  <App/>
</QueryParamProvider>

With this, all consumers could then simply do:

const [query, setQuery] = useQueryParams();

Benefits:

  • Improved developer experience, as folks have to write less code
  • Smaller bundle sizes
  • Possible perf gain if right now every hook deserializes rather than reading from a single cached source of truth (likely of no consequence for most apps tho')

Cons:

  • More API variations for folks to learn and think about
  • Perhaps others?

What do you think?

Thanks for reading!

Values not loading on a server side render using <StaticRouter>

I'm having trouble getting use-query-params to correctly fetch query param values on a Server Side Render. We are using <StaticRouter> from react-router-dom, initialised as is standard with the known value of location from the request.

Looking at the source here it seems that the assumption is being made that on a SSR the query must be extracted from location.pathname rather than from location.search. However AFAICT <StaticRouter> puts the query string in location.search consistent with <BrowserRouter>.

It seems like it might make more sense just to test directly for the presence of location.search and only fallback to parsing pathname if the former is missing, rather than checking for window? (although I'm not sure if this approach would be compatible with other routing frameworks...)

I will in the meantime workaround this using rawQuery but it'd be nice for it to work out of the box to reduce complexity and allow us to phase out other query string related packages completely in favour of just using this.

Cheers.

ArrayParam: empty string is valid value

Just want to start by saying thank you for the package! Has been a big help.

Something I'd consider a bug: with ArrayParam, setting ["", "foo"] will result in a value of ["foo"]. Empty strings are valid values, and this behavior is surprising. This should at the very least be documented, but I would argue that it's a bad default.

Aliasing queryParams

The old library, https://github.com/pbeshai/react-url-query, has a way to alias the query param using the queryParam option in initial config. So that the prop name and query param can be different.

Is there any way to do the same with the hooks based approach?

Really great library btw.

Can this work with Gatsby?

Has anyone successfully used this with gatsby?

I'm trying to get this working but upon running 'gatsby build. I get the following error...

WebpackError: TypeError: Cannot read property 'search' of undefined

I've looked at similar issues but have not found a definitive answer.

Undefined values causing problems with inputs

This might be an unintended use of the hook, but it's a use nonetheless.

I have a component Filter which the user interacts with through input controls in order to specify a filter, which should then be used for searching.

My goal is to have the filter saved to the url, so that the user may then link the search page, with the filter they specified. I figured that useQueryParams would cover this use-case as a kind of "drop-in replacement" for useState, which just so happens to load/save the state to the query string.

However, for parameters of the type StringParam, the useQueryParams hook seems to default "empty" values to undefined - seemingly in order to remove them from the query string - likely due to the parameter encoder.

This means that any state variables set by useQueryParams (or useQueryParam), will cause input controls to switch from being uncontrolled to controlled when the user enters an initial value, or vice-versa when the user deletes the value from the control, causing this warning:

Warning: A component is changing an uncontrolled input of type undefined to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://fb.me/react-controlled-components
    in input (at src/index.js:32)
    in Filter (at src/index.js:55)
    in Route (at src/index.js:54)
    in Route (created by QueryParamProvider)
    in QueryParamProvider (at src/index.js:53)
    in Router (created by BrowserRouter)
    in BrowserRouter (at src/index.js:52)
    in App (at src/index.js:63)

Now, I've seen #31, and the proposed solution by @albinekb seems workable for useQueryParam, but what about useQueryParams?

I'm not proposing adding default values for the query parameters, but I'd be happy to see some kind of solution to this problem. My simple idea of computing a displayFilter doesn't cut it.

Demo (with my failed attempt at solving the issue): https://codesandbox.io/s/bitter-moon-81gzo

Negative query param values not sent to the server

Hello,

When I'm sending negative values in query params on the server side I'm receiving nothing.

Examples:

sent: http://localhost/projecten/zoeken?risicoclassificatie=min-2_max-5 
received: {"risicoclassificatie":{"min":2,"max":5}}
sent: http://localhost/projecten/zoeken?risicoclassificatie=min-2_max-5&winstgevendheid=min-1_max--34
received: {"risicoclassificatie":{"min":2,"max":5}, "winstgevendheid":{"min":1}}
sent: http://localhost/projecten/zoeken?risicoclassificatie=min-2_max-5&winstgevendheid=min--76_max--34
received: {"risicoclassificatie":{"min":2,"max":5},"winstgevendheid":{}}

Please find attached a screenshot if it helps ... Thank you very much!

Screenshot from 2019-12-05 11-48-23

Query params not synced in parent component?

So, I am currently using 0.4.5.

And inside it I have something akin to this structure.

const [value, setValue] = useQueryParam('value', StringParam);
const [otherThing, setOtherThing] = setState<boolean>(false)
...
function flipOtherThing() = { setOtherThing(old => !old) }
...
return (
    <ValueGetterComponent externalSetterForValue={setValue} />
    {<p>{value}</p>}
    <button onClick={flipOtherThing} />
)

(if above doesn't work, don't worry, I just freebased the above code).


The problem is that whatever is running and setting things with setValue, doesn't seem to refresh/re-render the current component (parent) until the state (separate thing) is changed.

Where I actually have this problem is I have maybe 4-6 query paramas, the URL changes instantly, but not the internal state of the parent component, until I change some other state inside it by using a state setter. (example flipOtherThing above)
Once the state setter is called, all queryParams are in sync again.


Does my explanation make sense of what my problem is? Or should I try to explain it in some other way? :)

Is it possible to add an example with typescript?

First of all, thank you for your excellent work. It was never so easy to encode state in the url. Unfortunately, I'm struggling a bit to type the useQueryParams function in Typescript.
How do I have to define the two following functions to make the Typescript compiler pass?

  const [query, setQuery] = useQueryParams({
    modalUserTypeOpen: BooleanParam,
    userTypeId: StringParam
  });
  const updateQuery = (u: DecodedValueMap): void =>
    setQuery({...query, ...u});

Thank You!
Jan

Using with ES5 browsers?

Hi!

Unfortunately, I could not use this hook in older browsers. After assembly, the code contains conts that do not work.

I tried to add a package to the rules include section:

{
    test: /\.(js|jsx)$/,
    include: [
        path.resolve(__dirname, 'src'),
        path.resolve(__dirname, 'node_modules', 'use-query-params'),
    ],
    use: [
        'babel-loader',
    ],
},

But the result has not changed. What am I doing wrong? There were no similar problems with other npm packages.

Stringify Dates is not giving the expected result

Hello,
thanks for the library. I have a question or maybe misunderstanding of the configuration.

const [query, setQuery] = useQueryParams({
tags: ArrayParam, status: ArrayParam, createdFrom: DateParam, });

When I call
const encodedQuery = stringify(query);
the output is for example
"?createdFrom=Tue%20Nov%2005%202019%2001%3A00%3A00%…itteleurop%C3%A4ische%20Normalzeit%29&status=Open"

I would have expected the DateParam to be in the format YYYY-MM-DD like it is in the url. What do I need to configure in the stringify method to get this kind of formatting of the date?

Thanks for your help!

React Router - Concurrent updates fail

Hey,

Thanks for the library I've been using it extensively!

Versions:

use-query-params: 0.4.2
react-router: 5.1.2
react-router-dom: 5.1.2

When calling two setQuery functions from the same "event" only one is updated.

See this video
output_optimized

Reproducable code sample
https://codesandbox.io/embed/lucid-pike-g5vok

The usecase here is noddy but the implementation is the important part. In my use case I have two very different abstractions which is why they have seperate hooks and don't use useQueryParams instead

I believe it will be an issue with the way use-query-param uses react-routers Route component

I wanted to highlight the issue before investigating

Support for nested query parameters

I am creating a dashboard with multiple panels and was thinking it would be a good idea to support scoping of query parameters through a prefix.

eg. ?sidebar-panel[id1][width]=100&sidebar-panel[id2][is-editing]=true&body-tile[0][placement]=0,0&body-tile[1][placement]=0,1

qs library supports nesting and auto serializes/deserializes nested structures into query parameters like the above.

I think it would be useful to have scoping supported in this library so that it becomes easy to configure a component to receive the query parameters scoped by component name (and optionally an id, in case of multiple components in the page).

I believe this somewhat expands the scope of this library, but if you are ok with having this in the core, I would be interested in contributing the implementation for this.

I am not yet entirely sure of what the ideal API would look like, but I was thinking along the line of templated keys (similar to webpack):

const [foo, setFoo] = useQueryParam('[prefix].[id].foo', ArrayParam, { prefix: 'custom-component', id: this.props.id });

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

TypeError: prevDeps is undefined

Hello again,

I'm getting this strange error with the following code:

  const { filtersURL, filters: defaultFilters } = props;

  const [filters, setFilters] = useState(defaultFilters);

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios.get(filtersURL);

      if (result.data) {
        setFilters(result.data);
      }
    };
    fetchData();
  }, [filtersURL]);

  const queryParamsFromFilters = getQueryParamsFromFilters({
    filters: filters
  });

  const [queryParams, setQueryParams] = useQueryParams(queryParamsFromFilters);

getQueryParamsFromFilters returns a valid initialization object:

test("Collects the query params from filters", () => {
  expect(getQueryParamsFromFilters({ filters: filters })).toStrictEqual({
    q: StringParam,
    location: ArrayParam
  });
});

The error message log referring to useQueryParams is:

useQueryParam
node_modules/use-query-params/esm/useQueryParam.js:33

  30 | 
  31 | 
  32 | var refHistory = React.useRef(history);
> 33 | React.useEffect(function () {
     | ^  34 |   refHistory.current = history;
  35 | }, [history]); // ref with current version location object (see #46)
  36 | 

useQueryParams/paramValues<
node_modules/use-query-params/esm/useQueryParams.js:63

  60 | 
  61 | var paramNames = Object.keys(paramConfigMap);
  62 | var paramValues = paramNames.map(function (paramName) {
> 63 |   return useQueryParam(paramName, paramConfigMap[paramName], rawQuery)[0];
     | ^  64 | }); // we use a memo here to prevent recreating the containing decodedValues object
  65 | // which would break === comparisons even if no values changed.
  66 | 

useQueryParams
node_modules/use-query-params/esm/useQueryParams.js:62

  59 | // we reuse the logic to not recreate objects
  60 | 
  61 | var paramNames = Object.keys(paramConfigMap);
> 62 | var paramValues = paramNames.map(function (paramName) {
     | ^  63 |   return useQueryParam(paramName, paramConfigMap[paramName], rawQuery)[0];
  64 | }); // we use a memo here to prevent recreating the containing decodedValues object
  65 | // which would break === comparisons even if no values changed.

And I also have a warning in the console:

Warning: React has detected a change in the order of Hooks called by Filters. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://fb.me/rules-of-hooks

   Previous render            Next render
   ------------------------------------------------------
1. useState                   useState
2. useEffect                  useEffect
3. useContext                 useContext
4. useRef                     useRef
5. useEffect                  useEffect
6. useMemo                    useMemo
7. useRef                     useRef
8. useEffect                  useEffect
9. useRef                     useRef
10. useEffect                 useEffect
11. useMemo                   useMemo
12. useContext                useContext
13. useRef                    useRef
14. useEffect                 useEffect
15. useRef                    useRef
16. useEffect                 useEffect
17. useMemo                   useMemo
18. useCallback               useCallback
19. useMemo                   useContext
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    in Filters (at App.js:11)
    in Route (created by QueryParamProvider)
    in QueryParamProvider (at App.js:10)
    in Router (created by BrowserRouter)
    in BrowserRouter (at App.js:9)
    in App (at src/index.js:6)

And I've saved a dedicated branch to help debug this issue: https://github.com/metamn/use-query-filters/tree/3-wrong-use-query-params

I hope it helps. I'm thinking that useQueryParams is not ready to handle async environments where the init params are not known a priori but are loaded from the database with a delay. Please see also #65

Thanks a lot!

Asynchronous update support

I like this library's API, but as far as I can tell it always pushes updates synchronously into the URL. That's obviously ideal for correctness, but in my app we have some state that updates very frequently (i.e. 60/s) that should be "eventually consistent" with the URL. I tried synchronous updates, but at that frequency they're just too slow. I've introduced some intermediate state with a 100ms debounce to solve for this -- if use-query-params supported something like that, I might be able to use it instead of my homegrown solution.

Investigate saving serialized values in context

Consider the following setup:

<A>
  <B />
  <C />
</A>

If components B and C both want to read the same complex object (e.g. array or object) from the URL, they'll have two different instances. This might lead to some unexpected problems. A solution is to move the useQueryParams to their closest common parent and then pass it in via props or a userland context.

If we can be smarter with the context we already have, we may be able to avoid this entirely.

useMemo should not be used as a semantic guarantee

I really like the change to memoize the decoded values to maintain referential equality! However, reading through the React docs about useMemo, it seems as if code should not rely on useMemo for referential equality:

You may rely on useMemo as a performance optimization, not as a semantic guarantee. In the future, React may choose to “forget” some previously memoized values and recalculate them on next render, e.g. to free memory for offscreen components. Write your code so that it still works without useMemo — and then add it to optimize performance.

Therefore, I would be concerned that the below code might break at some point.

const decodedValue = React.useMemo(() => {

const decodedValues = React.useMemo(() => {

It would be great to keep this feature of returning the same object if the query parameters have not changed. I frequently use the result of useQueryParam(s) in a useEffect hook that I only want to run when the query parameters have actually changed. Without this memoization, the useEffect could become a "runaway" if it causes the component to re-render and call useEffect again.

This isn't a critical issue since it seems to be working fine for now. However, I think it's worth looking into potential alternatives that provide the semantic guarantee.

Array props cause extra renders with useQueryParams

Hi. It seems useQueryParams doesn't do a deep comparison on array params, resulting in a refresh even when the param itself doesn't change.

My use case is something like this...

const ComponentWithArrayParam = memo(() => { 
  const [queryWith] = useQueryParams(['arrayParam']);
  return (<div/>);
});

const ComponentWithoutArrayParam = memo(() => { 
  const [queryWithout, setQueryWithout] = useQueryParams(['stringParam']);

  useEffect(() => {
    // NOTE: this causes ComponentWithArrayParam to update...
    setQueryWithout({stringParam: 'newString'}, 'replaceIn');
  }, []);

  return (<div/>);
});

function App() {
  return (
    <>
      <ComponentWithArrayParam/>
      <ComponentWithoutArrayParam/>
    </>
  );
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.