diff --git a/README.md b/README.md
index cc1728fafd..9eea9c9417 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@
- [`useIdle`](./docs/useIdle.md) — tracks whether user is being inactive.
- [`useIntersection`](./docs/useIntersection.md) — tracks an HTML element's intersection. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-useintersection--demo)
- [`useKey`](./docs/useKey.md), [`useKeyPress`](./docs/useKeyPress.md), [`useKeyboardJs`](./docs/useKeyboardJs.md), and [`useKeyPressEvent`](./docs/useKeyPressEvent.md) — track keys. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usekeypressevent--demo)
- - [`useLocation`](./docs/useLocation.md) and [`useSearchParam`](./docs/useSearchParam.md) — tracks page navigation bar location state.
+ - [`useLocation`](./docs/useLocation.md), [`useSearchParam`](./docs/useSearchParam.md), and [`useAllSearchParam`](./docs/useAllSearchParam.md) — tracks page navigation bar location state.
- [`useLongPress`](./docs/useLongPress.md) — tracks long press gesture of some element.
- [`useMedia`](./docs/useMedia.md) — tracks state of a CSS media query. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/sensors-usemedia--demo)
- [`useMediaDevices`](./docs/useMediaDevices.md) — tracks state of connected hardware devices.
diff --git a/docs/useAllSearchParam.md b/docs/useAllSearchParam.md
new file mode 100644
index 0000000000..ca6fa428c8
--- /dev/null
+++ b/docs/useAllSearchParam.md
@@ -0,0 +1,30 @@
+# `useAllSearchParam`
+
+React sensor hook that tracks all of the browser's location search params as an object.
+
+## Usage
+
+```jsx
+import {useAllSearchParam} from 'react-use';
+
+const Demo = () => {
+ const params = useAllSearchParam();
+
+ return (
+
+
params: {JSON.stringify(params)}
+
+
+
+
+
+
+
+ );
+};
+```
+
+## Caveats/Gotchas
+
+- Only the first value is kept when a param is repeated (e.g. `?foo=bar&foo=baz` yields `{foo: 'bar'}`), matching the semantics of `useSearchParam`.
+- When using a hash router, like `react-router`'s [``](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/HashRouter.md), this hook won't be able to read the search parameters as they are considered part of the hash of the URL by browsers.
diff --git a/src/index.ts b/src/index.ts
index 62b69356b7..96698c5736 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -2,6 +2,7 @@ export { default as createMemo } from './factory/createMemo';
export { default as createReducerContext } from './factory/createReducerContext';
export { default as createReducer } from './factory/createReducer';
export { default as createStateContext } from './factory/createStateContext';
+export { default as useAllSearchParam } from './useAllSearchParam';
export { default as useAsync } from './useAsync';
export { default as useAsyncFn } from './useAsyncFn';
export { default as useAsyncRetry } from './useAsyncRetry';
diff --git a/src/useAllSearchParam.ts b/src/useAllSearchParam.ts
new file mode 100644
index 0000000000..337d75b7cd
--- /dev/null
+++ b/src/useAllSearchParam.ts
@@ -0,0 +1,44 @@
+import { useEffect, useState } from 'react';
+import { isBrowser, off, on } from './misc/util';
+
+const getAllParams = (search: string): Record => {
+ const params = new URLSearchParams(search);
+ const result: Record = {};
+
+ params.forEach((value, key) => {
+ if (!(key in result)) {
+ result[key] = value;
+ }
+ });
+
+ return result;
+};
+
+export type UseAllSearchParam = () => Record;
+
+const useAllSearchParam: UseAllSearchParam = () => {
+ const location = window.location;
+ const [params, setParams] = useState>(() => getAllParams(location.search));
+
+ useEffect(() => {
+ const onChange = () => {
+ setParams(getAllParams(location.search));
+ };
+
+ on(window, 'popstate', onChange);
+ on(window, 'pushstate', onChange);
+ on(window, 'replacestate', onChange);
+
+ return () => {
+ off(window, 'popstate', onChange);
+ off(window, 'pushstate', onChange);
+ off(window, 'replacestate', onChange);
+ };
+ }, []);
+
+ return params;
+};
+
+const useAllSearchParamServer: UseAllSearchParam = () => ({});
+
+export default isBrowser ? useAllSearchParam : useAllSearchParamServer;
diff --git a/stories/useAllSearchParam.story.tsx b/stories/useAllSearchParam.story.tsx
new file mode 100644
index 0000000000..4109f00461
--- /dev/null
+++ b/stories/useAllSearchParam.story.tsx
@@ -0,0 +1,31 @@
+import { storiesOf } from '@storybook/react';
+import * as React from 'react';
+import { useAllSearchParam } from '../src';
+import ShowDocs from './util/ShowDocs';
+
+const Demo = () => {
+ const params = useAllSearchParam();
+
+ return (
+
+
params: {JSON.stringify(params)}
+
+
+
+
+
+
+
+ );
+};
+
+storiesOf('Sensors/useAllSearchParam', module)
+ .add('Docs', () => )
+ .add('Demo', () => );
diff --git a/tests/useAllSearchParam.test.ts b/tests/useAllSearchParam.test.ts
new file mode 100644
index 0000000000..880ab613a4
--- /dev/null
+++ b/tests/useAllSearchParam.test.ts
@@ -0,0 +1,69 @@
+import { act, renderHook } from '@testing-library/react-hooks';
+import useAllSearchParam from '../src/useAllSearchParam';
+
+const { location } = window;
+
+let mockSearch: string;
+
+beforeEach(() => {
+ delete (window as any).location;
+ const { search, ...restLocation } = location;
+
+ // @ts-ignore
+ window.location = { ...restLocation };
+
+ Object.defineProperty(window.location, 'search', {
+ get: function () {
+ return mockSearch;
+ },
+ });
+});
+
+it('returns all search params as an object', () => {
+ mockSearch = 'foo=bar&baz=quux';
+
+ const { result } = renderHook(() => useAllSearchParam());
+
+ expect(result.current).toEqual({ foo: 'bar', baz: 'quux' });
+});
+
+it('returns an empty object when there are no search params', () => {
+ mockSearch = '';
+
+ const { result } = renderHook(() => useAllSearchParam());
+
+ expect(result.current).toEqual({});
+});
+
+it('tracks the latest search params', () => {
+ mockSearch = 'foo=bar&baz=quux';
+
+ let callback;
+ const window$addEventListener = window.addEventListener;
+ window.addEventListener = (event, cb) => {
+ if (event === 'pushstate') {
+ callback = cb;
+ }
+ };
+
+ const { result } = renderHook(() => useAllSearchParam());
+
+ expect(result.current).toEqual({ foo: 'bar', baz: 'quux' });
+
+ act(() => {
+ mockSearch = 'foo=1&baz=2';
+ callback();
+ });
+
+ expect(result.current).toEqual({ foo: '1', baz: '2' });
+
+ window.addEventListener = window$addEventListener;
+});
+
+it('keeps the first value for duplicate keys', () => {
+ mockSearch = 'foo=bar&foo=baz';
+
+ const { result } = renderHook(() => useAllSearchParam());
+
+ expect(result.current).toEqual({ foo: 'bar' });
+});