React Router hooks with query string parsing
npm install react-router-query-hooks!Test
A small package that augments the basic react-router-dom hooks (useLocation and useHistory) to be more query string aware by parsing using the query-string library.
Primarily, it exports a simple useQueryParams hook for reading and manipulating the URL query string (useful for UIs where sort order, or page number is encoded in the URL). See the usage notes for more details.
Depends on:
- query-string
- Peer Dependency react-router-dom@^5.1 or greater (needs router hooks)
This package is also written in ES6, so to use, you'll need some transpiler such as babel to run on node_modules.
``bash`
$ npm install react-router-query-hooks react-router-dom
or
`bash`
$ yarn add react-router-query-hooks react-router-dom
There are two main uses for this package:
1. The higher-level useQueryParams hook.useLocation
2. The lower-level replacements for or useHistory: useLocationWithQuery and useHistoryWithQuery.
Note: With both usages, you'll need to have your components wrapped in a Router (any flavor exported by React Router should work).
The easiest way to use this library is to use the higher-level useQueryParams hook. It's useful for reading or updating the current query parameters from the URL. Behind the scenes, it's basically a sweetened version of the useHistoryWithQuery hook outlined in the next section.
`jsx
import { useQueryParams } from "react-router-query-hooks";
const MyComponentInRouter = () => {
const [query, { pushQuery, replaceQuery }] = useQueryParams();
};
`
The return value for the useQueryParams hook is similar to React's useState; however it includes _two_ setters instead of one: pushQuery and replaceQuery—to either add a history entry or replace the current history entry respectively. In the above example the first entry in the tuple (query) contains the parsed query parameters from the URL.
If you want to use some more primitive hooks, the useQueryParams hook builds upon the two hooks that wrap the underlying React Router hooks (useLocation, and useHistory).
#### useLocationWithQuery
This modified hook adds the query key to React Router's location object which contains a parsed version of location.search. As with useLocation it is read-only (has no setters).
`jsx
import { useLocation } from "react-router-dom";
import { useLocationWithQuery } from "react-router-query-hooks";
const MyComponentInRouter = () => {
// const location = useLocation();
const location = useLocationWithQuery(); // Same interface as above but with location.query
/* Augmented location object:
{
key: 'ac3df4', // not with HashHistory!
pathname: '/somewhere',
search: '?some=search-string',
query: {
some: "search-string" // <- ADDED PROPERTY
},
hash: '#howdy',
state: {
[userDefined]: true
}
}
*/
};
`
#### useHistoryWithQuery
This modified hook builds upon React Router's history object and the above location additions:
- In history.location, it adds the query key to React Router's location object (as above)query
- Furthermore, it supports URL updates with the key. So history.replace supports both paths (as before) and location objects with the query key.history.location
- GOTCHA: React router does NOT trigger an update to when the location changes. While the _internal_ history object is updated, this is a mutation, and React will not update the component. You must listen to the location separately using useLocationWithQuery or the combined useQueryParams to update the component upon a location change.
`jsx
import { useHistory } from "react-router-dom";
import { useLocationWithQuery } from "react-router-query-hooks";
const MyComponentInRouter = () => {
// const history = useHistory();
const history = useHistoryWithQuery();
// Supports push & replace with query object in location (along with supporting the existing API):
return (
<>
>
);
};
`
- None of the hooks shadow the URL state—everything is read from the URL.
The motivation for this package was to have an easy way to maintain URL query parameters, that was simple, somewhat light, and used the URL as the source of truth. Furthermore, it was designed to support UIs that have pagination:
`jsx
import React from "react";
import { useQueryParams } from "react-router-query-hooks";
const MyComponentInRouter = () => {
const [query, { replaceQuery }] = useQueryParams();
const { page, sortBy, order } = query;
// Useful for calling APIs with a page and sort order
const { data } = useAPI("/my-resource", { page, sortBy, order });
return (
Optimization
Sometimes you don't want to recreate a callback on every render. Consider the example above: on each render,
onHeaderClick callback is redefined. The next optimization (if needed) is to wrap that callback in useCallback:`jsx
import React, { useCallback } from "react";
import { useQueryParams } from "react-router-query-hooks";const MyComponentInRouter = () => {
const [query, { replaceQuery }] = useQueryParams();
const { page, sortBy, order } = query;
// Useful for calling APIs with a page and sort order
const { data } = useAPI("/my-resource", { page, sortBy, order });
const onClick = useCallback(
(sortBy, order) => replaceQuery({ ...query, sortBy, order }),
[query]
);
return (
replaceQuery({ ...query, page })} />
);
};
`That's a good start: on each render we'll use the same
onClick callback and only redefine if query changes. In some cases, query may change _frequently_. The next optimization we can make is to update using a _function_ (this should look familiar when using useState):`js
const onClick = useCallback(
(sortBy, order) =>
replaceQuery(currentQuery => ({ ...currentQuery, sortBy, order })),
[]
);
`Now, our callback doesn't have any dependencies, and will be reused for each render! This method of changing the state works for both
replaceQuery and pushQuery`.