Skip to content

Commit

Permalink
Update to handle null and empty values
Browse files Browse the repository at this point in the history
  • Loading branch information
pbeshai committed Apr 16, 2020
1 parent 9beacfe commit 4749dfe
Show file tree
Hide file tree
Showing 14 changed files with 1,962 additions and 1,679 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
language: node_js
node_js:
- "10"
- "12"
cache: npm
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ When creating apps with easily shareable URLs, you often want to encode state as
Using npm:

```
$ npm install --save use-query-params
$ npm install --save use-query-params query-string
```

Note: There is a peer dependency on [query-string](https://github.com/sindresorhus/query-string). For IE11 support, use v5.1.1, otherwise use v6.

Link your routing system (e.g., [React Router example](https://github.com/pbeshai/use-query-params/blob/master/examples/react-router/src/index.tsx), [Reach Router example](https://github.com/pbeshai/use-query-params/blob/master/examples/reach-router/src/index.tsx)):

```js
Expand All @@ -54,6 +56,8 @@ ReactDOM.render(

### Usage

Be sure to add **QueryParamProvider** as shown in Installation above.

Add the hook to your component. There are two options: `useQueryParam`:

```js
Expand Down Expand Up @@ -87,16 +91,17 @@ import {
StringParam,
NumberParam,
ArrayParam,
withDefault,
} from 'use-query-params';

const UseQueryParamsExample = () => {
// something like: ?x=123&q=foo&filters=a&filters=b&filters=c in the URL
const [query, setQuery] = useQueryParams({
x: NumberParam,
q: StringParam,
filters: ArrayParam,
filters: withDefault(ArrayParam, []),
});
const { x: num, q: searchQuery, filters = [] } = query;
const { x: num, q: searchQuery, filters } = query;

return (
<div>
Expand Down Expand Up @@ -130,10 +135,11 @@ import {
StringParam,
NumberParam,
ArrayParam,
withDefault,
} from 'use-query-params';

const WithQueryParamsExample = ({ query, setQuery }: any) => {
const { x: num, q: searchQuery, filters = [] } = query;
const { x: num, q: searchQuery, filters } = query;

return (
<div>
Expand All @@ -158,7 +164,7 @@ const WithQueryParamsExample = ({ query, setQuery }: any) => {
export default withQueryParams({
x: NumberParam,
q: StringParam,
filters: ArrayParam,
filters: withDefault(ArrayParam, []),
}, WithQueryParamsExample);
```

Expand All @@ -171,19 +177,20 @@ import {
StringParam,
NumberParam,
ArrayParam,
withDefault,
} from 'use-query-params';

const RenderPropsExample = () => {
const queryConfig = {
x: NumberParam,
q: StringParam,
filters: ArrayParam,
filters: withDefault(ArrayParam, []),
};
return (
<div>
<QueryParams config={queryConfig}>
{({ query, setQuery }) => {
const { x: num, q: searchQuery, filters = [] } = query;
const { x: num, q: searchQuery, filters } = query;
return (
<>
<h1>num is {num}</h1>
Expand Down Expand Up @@ -238,7 +245,7 @@ A few basic [examples](https://github.com/pbeshai/use-query-params/tree/master/e
- [Type Definitions](https://github.com/pbeshai/use-query-params/blob/master/src/types.ts) and from [serialize-query-params](https://github.com/pbeshai/serialize-query-params/blob/master/src/types.ts).
- [Serialization Utility Functions](https://github.com/pbeshai/serialize-query-params/blob/master/src/serialize.ts)

For convenience, use-query-params exports all of the [serialize-query-params](https://github.com/pbeshai/serialize-query-params) library. This includes most functions from [query-string](https://github.com/sindresorhus/query-string), which is used internally.
For convenience, use-query-params exports all of the [serialize-query-params](https://github.com/pbeshai/serialize-query-params) library.

#### UrlUpdateType

Expand Down
1 change: 1 addition & 0 deletions examples/react-router/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"@types/react": "^16.8.7",
"@types/react-dom": "^16.8.2",
"@types/react-router-dom": "^4.3.1",
"query-string": "^6.12.1",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-router-dom": "^4.3.1",
Expand Down
6 changes: 4 additions & 2 deletions examples/react-router/src/ReadmeExample2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import {
StringParam,
NumberParam,
ArrayParam,
QueryParamConfig,
withDefault,
} from 'use-query-params';

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

return (
<div>
Expand Down
5 changes: 3 additions & 2 deletions examples/react-router/src/ReadmeExample3.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import {
StringParam,
NumberParam,
ArrayParam,
withDefault,
} from 'use-query-params';

const UseQueryParamsExample = ({ query, setQuery }: any) => {
const { x: num, q: searchQuery, filters = [] } = query;

console.log('got filters =', filters, query);
return (
<div>
<h1>num is {num}</h1>
Expand All @@ -33,7 +34,7 @@ export default withQueryParams(
{
x: NumberParam,
q: StringParam,
filters: ArrayParam,
filters: withDefault(ArrayParam, []),
},
UseQueryParamsExample
);
3 changes: 2 additions & 1 deletion examples/react-router/src/ReadmeExample4.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import {
StringParam,
NumberParam,
ArrayParam,
withDefault,
} from 'use-query-params';

const RenderPropsExample = () => {
const queryConfig = {
x: NumberParam,
q: StringParam,
filters: ArrayParam,
filters: withDefault(ArrayParam, []),
};
return (
<div>
Expand Down
6 changes: 3 additions & 3 deletions examples/react-router/src/UseQueryParamExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {

const MyParam = {
encode: (val: number) => `MY_${val}`,
decode: (input: string | string[] | undefined) => {
decode: (input: string | (string| null)[] | null | undefined) => {
const str = input instanceof Array ? input[0] : input;
return str == null ? undefined : +str.split('_')[1];
},
Expand Down Expand Up @@ -84,7 +84,7 @@ const UseQueryParamExample = () => {
</tr>
<tr>
<td>anyp</td>
<td>{anyp}</td>
<td>{anyp as any}</td>
<td>{typeof anyp}</td>
<td>
<button
Expand All @@ -107,7 +107,7 @@ const UseQueryParamExample = () => {
<td>arr</td>
<td>
{arr
? arr.map((d: string, i: number) => (
? arr.map((d: string | null, i: number) => (
<div key={i}>
arr[{i}] = {d}
</div>
Expand Down
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,31 @@
"author": "Peter Beshai <peter.beshai@gmail.com>",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.3.3",
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"@babel/preset-react": "^7.9.4",
"@babel/preset-typescript": "^7.9.0",
"@testing-library/jest-dom": "^4.1.0",
"@testing-library/react": "^9.1.4",
"@types/jest": "^24.0.11",
"@types/react": "^16.8.25",
"@typescript-eslint/eslint-plugin": "^2.2.0",
"@typescript-eslint/parser": "^2.2.0",
"babel-jest": "^24.5.0",
"babel-jest": "^25.3.0",
"eslint": "^6.3.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-react": "^7.14.3",
"eslint-plugin-react-hooks": "^2.1.2",
"husky": "^3.0.5",
"jest": "^24.5.0",
"jest": "^25.3.0",
"lint-staged": "^9.2.5",
"prettier": "^1.18.2",
"prettier": "^2.0.4",
"query-string": "^6.12.1",
"react": "^16.9.0",
"react-dom": "^16.9.0",
"react-hooks-testing-library": "^0.3.7",
"rimraf": "^3.0.0",
"typescript": "^3.3.3333"
"rimraf": "^3.0.2",
"typescript": "^3.8.3"
},
"peerDependencies": {
"react": ">=16.8.0",
Expand Down
7 changes: 2 additions & 5 deletions src/__tests__/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import {
stringify,
parse as parseQueryString,
EncodedQueryWithNulls,
} from 'serialize-query-params';
import { EncodedQueryWithNulls } from 'serialize-query-params';
import { stringify, parse as parseQueryString } from 'query-string';

// if passed a location, will mutate it so we can see what changes are being made
export function makeMockHistory(location: any = {}) {
Expand Down
35 changes: 33 additions & 2 deletions src/__tests__/updateUrlQuery-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ describe('updateUrlQuery', () => {
const history = makeMockHistory();
// test updating existing query param
updateUrlQuery(
{ foo: '123' },
makeMockLocation({ foo: '521', bar: 'zzz' }),
{ foo: '123', emp: '', nul: null, und: undefined },
makeMockLocation({
foo: '521',
bar: 'zzz',
emp: 'emp',
nul: 'nul',
und: 'und',
}),
history,
'replaceIn'
);
Expand All @@ -22,13 +28,27 @@ describe('updateUrlQuery', () => {
expect(calledReplaceQuery(history, 0)).toEqual({
foo: '123',
bar: 'zzz',
emp: '',
nul: null,
// und is removed
});

// test when no query params
updateUrlQuery({ foo: '123' }, makeMockLocation({}), history, 'replaceIn');
expect(calledReplaceQuery(history, 1)).toEqual({
foo: '123',
});

// test when removing query params
updateUrlQuery(
{ foo: undefined },
makeMockLocation({ foo: '123', bar: 'zzz' }),
history,
'replaceIn'
);
expect(calledReplaceQuery(history, 2)).toEqual({
bar: 'zzz',
});
});

it('pushIn', () => {
Expand All @@ -53,6 +73,17 @@ describe('updateUrlQuery', () => {
expect(calledPushQuery(history, 1)).toEqual({
foo: '123',
});

// test when removing query params
updateUrlQuery(
{ foo: undefined },
makeMockLocation({ foo: '123', bar: 'zzz' }),
history,
'pushIn'
);
expect(calledPushQuery(history, 2)).toEqual({
bar: 'zzz',
});
});

it('replace', () => {
Expand Down
7 changes: 3 additions & 4 deletions src/useQueryParam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import {
parse as parseQueryString,
parseUrl as parseQueryURL,
stringify,
} from 'query-string';

import {
EncodedQueryWithNulls,
StringParam,
QueryParamConfig,
Expand Down Expand Up @@ -87,10 +90,6 @@ export const useQueryParam = <D, D2 = D>(
// decode if the encoded value has changed, otherwise
// re-use memoized value
const decodedValue = React.useMemo(() => {
if (encodedValue == null) {
return undefined;
}

return paramConfig.decode(encodedValue);
}, [arraySafeEncodedValue, paramConfig]); // eslint-disable-line react-hooks/exhaustive-deps

Expand Down
4 changes: 2 additions & 2 deletions src/useQueryParams.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { parse as parseQueryString } from 'query-string';
import {
parse as parseQueryString,
encodeQueryParams,
EncodedQueryWithNulls,
DecodedValueMap,
Expand Down Expand Up @@ -74,7 +74,7 @@ export const useQueryParams = <QPCMap extends QueryParamConfigMap>(
// we reuse the logic to not recreate objects
const paramNames = Object.keys(paramConfigMap);
const paramValues = paramNames.map(
paramName =>
(paramName) =>
useQueryParam(paramName, paramConfigMap[paramName], rawQuery)[0]
);

Expand Down
8 changes: 4 additions & 4 deletions src/withQueryParams.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,17 @@ export function withQueryParams<
P extends InjectedQueryProps<QPCMap>
>(paramConfigMap: QPCMap, WrappedComponent: React.ComponentType<P>) {
// return a FC that takes props excluding query and setQuery
const Component: React.FC<Diff<P, InjectedQueryProps<QPCMap>>> = props => {
const Component: React.FC<Diff<P, InjectedQueryProps<QPCMap>>> = (props) => {
const [query, setQuery] = useQueryParams(paramConfigMap);

// see https://github.com/microsoft/TypeScript/issues/28938#issuecomment-450636046 for why `...props as P`
return (
<WrappedComponent query={query} setQuery={setQuery} {...(props as P)} />
);
};
Component.displayName = `withQueryParams(${WrappedComponent.displayName ||
WrappedComponent.name ||
'Component'})`;
Component.displayName = `withQueryParams(${
WrappedComponent.displayName || WrappedComponent.name || 'Component'
})`;

return Component;
}
Expand Down
Loading

0 comments on commit 4749dfe

Please sign in to comment.