Repository: streamich/react-use
Pull request #654 (2019-10-07 09:44:07)

Add reset util to useList
`useMap` has a `reset` callback that resets the state to its initial value which I was missing in `useList` so I added it here. I also added tests and updated the docs and story.

I also added memoization of the callback utils and aded tests for this.

Last but not least I was thinking about removing `clear` because you mostly need to be able to clear the array when you're starting with an empty array in the first place. The `reset` callback proposed here results in the same behaviour as it resets to the initial (default) value `[]`. I haven't implemented it as I wanted to know what others think


Patch:
diff --git a/README.md b/README.md
index 5e1b99f7..3ba51e52 100644
--- a/README.md
+++ b/README.md
@@ -131,7 +131,7 @@
   - [`useStateList`](./docs/useStateList.md) &mdash; circularly iterates over an array. [![][img-demo]](https://codesandbox.io/s/bold-dewdney-pjzkd)
   - [`useToggle` and `useBoolean`](./docs/useToggle.md) &mdash; tracks state of a boolean. [![][img-demo]](https://codesandbox.io/s/focused-sammet-brw2d)
   - [`useCounter` and `useNumber`](./docs/useCounter.md) &mdash; tracks state of a number. [![][img-demo]](https://streamich.github.io/react-use/?path=/story/state-usecounter--demo)
-  - [`useList`](./docs/useList.md) &mdash; tracks state of an array. [![][img-demo]](https://codesandbox.io/s/wonderful-mahavira-1sm0w)
+  - [`useList`](./docs/useList.md) and [`useUpsert`](./docs/useUpsert.md) &mdash; tracks state of an array. [![][img-demo]](https://codesandbox.io/s/wonderful-mahavira-1sm0w)
   - [`useMap`](./docs/useMap.md) &mdash; tracks state of an object. [![][img-demo]](https://codesandbox.io/s/quirky-dewdney-gi161)
 
 
diff --git a/docs/useList.md b/docs/useList.md
index b2dad670..2e33e188 100644
--- a/docs/useList.md
+++ b/docs/useList.md
@@ -8,7 +8,7 @@ React state hook that tracks a value of an array.
 import {useList} from 'react-use';
 
 const Demo = () => {
-  const [list, { clear, filter, push, remove, set, sort, updateAt }] = useList();
+  const [list, { clear, filter, push, remove, set, sort, updateAt, reset }] = useList();
 
   return (
     <div>
@@ -19,9 +19,14 @@ const Demo = () => {
       <button onClick={() => filter(item => item % 2 === 0)}>Filter even values</button>
       <button onClick={() => sort((a, b) => a - b)}>Sort ascending</button>
       <button onClick={() => sort((a, b) => b - a)}>Sort descending</button>
-      <button onClick={() => clear()}>Clear</button>
+      <button onClick={clear}>Clear</button>
+      <button onClick={reset}>Reset</button>
       <pre>{JSON.stringify(list, null, 2)}</pre>
     </div>
   );
 };
 ```
+
+## Related hooks
+
+- [useUpsert](./useUpsert.md)
diff --git a/docs/useSearchParam.md b/docs/useSearchParam.md
index f1184676..6d9fe29c 100644
--- a/docs/useSearchParam.md
+++ b/docs/useSearchParam.md
@@ -2,7 +2,6 @@
 
 React sensor hook that tracks browser's location search param.
 
-
 ## Usage
 
 ```jsx
@@ -27,3 +26,7 @@ const Demo = () => {
   );
 };
 ```
+
+## Caveats/Gotchas
+
+When using a hash router, like `react-router`'s [`<HashRouter>`](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/docs/useUpsert.md b/docs/useUpsert.md
index 2384660d..48c3de55 100644
--- a/docs/useUpsert.md
+++ b/docs/useUpsert.md
@@ -1,6 +1,7 @@
 # `useUpsert`
 
-Superset of `useList`. Provides an additional method to upsert (update or insert) an element into the list. 
+Superset of [`useList`](./useList.md). Provides an additional method to upsert (update or insert) an element into the list.
+
 ## Usage
 
 ```jsx
@@ -26,3 +27,7 @@ const Demo = () => {
   );
 };
 ```
+
+## Related hooks
+
+- [useList](./useList.md)
diff --git a/src/__stories__/useList.story.tsx b/src/__stories__/useList.story.tsx
index f720447b..2ccfb611 100644
--- a/src/__stories__/useList.story.tsx
+++ b/src/__stories__/useList.story.tsx
@@ -4,7 +4,7 @@ import { useList } from '..';
 import ShowDocs from './util/ShowDocs';
 
 const Demo = () => {
-  const [list, { clear, filter, push, remove, set, sort, updateAt }] = useList();
+  const [list, { clear, filter, push, remove, set, sort, updateAt, reset }] = useList([1, 2, 3, 4, 5]);
 
   return (
     <div>
@@ -15,7 +15,8 @@ const Demo = () => {
       <button onClick={() => filter(item => item % 2 === 0)}>Filter even values</button>
       <button onClick={() => sort((a, b) => a - b)}>Sort ascending</button>
       <button onClick={() => sort((a, b) => b - a)}>Sort descending</button>
-      <button onClick={() => clear()}>Clear</button>
+      <button onClick={clear}>Clear</button>
+      <button onClick={reset}>Reset</button>
       <pre>{JSON.stringify(list, null, 2)}</pre>
     </div>
   );
diff --git a/src/useList.ts b/src/useList.ts
index 5e292987..50000818 100644
--- a/src/useList.ts
+++ b/src/useList.ts
@@ -1,4 +1,4 @@
-import { useState } from 'react';
+import { useState, useMemo } from 'react';
 
 export interface Actions<T> {
   set: (list: T[]) => void;
@@ -8,14 +8,14 @@ export interface Actions<T> {
   push: (item: T) => void;
   filter: (fn: (value: T) => boolean) => void;
   sort: (fn?: (a: T, b: T) => number) => void;
+  reset: () => void;
 }
 
 const useList = <T>(initialList: T[] = []): [T[], Actions<T>] => {
   const [list, set] = useState<T[]>(initialList);
 
-  return [
-    list,
-    {
+  const utils = useMemo<Actions<T>>(
+    () => ({
       set,
       clear: () => set([]),
       updateAt: (index, entry) =>
@@ -24,8 +24,12 @@ const useList = <T>(initialList: T[] = []): [T[], Actions<T>] => {
       push: entry => set(currentList => [...currentList, entry]),
       filter: fn => set(currentList => currentList.filter(fn)),
       sort: (fn?) => set(currentList => [...currentList].sort(fn)),
-    },
-  ];
+      reset: () => set([...initialList]),
+    }),
+    [set]
+  );
+
+  return [list, utils];
 };
 
 export default useList;


Test patch:
diff --git a/src/__tests__/useList.test.ts b/src/__tests__/useList.test.ts
index 047e6e68..f8dc9584 100644
--- a/src/__tests__/useList.test.ts
+++ b/src/__tests__/useList.test.ts
@@ -16,6 +16,7 @@ it('should init list and utils', () => {
     push: expect.any(Function),
     filter: expect.any(Function),
     sort: expect.any(Function),
+    reset: expect.any(Function),
   });
 });
 
@@ -150,3 +151,43 @@ it('should sort current list by provided function', () => {
   expect(result.current[0]).toEqual(['March', 'Jan', 'Feb', 'Dec']);
   expect(result.current[0]).not.toBe(initList); // checking immutability
 });
+
+it('should reset the list to initial list provided', () => {
+  const initList = [1, 2, 3];
+  const { result } = setUp(initList);
+  const [, utils] = result.current;
+
+  act(() => {
+    utils.push(4);
+  });
+
+  expect(result.current[0]).toEqual([1, 2, 3, 4]);
+
+  act(() => {
+    utils.reset();
+  });
+
+  expect(result.current[0]).toEqual([1, 2, 3]);
+  expect(result.current[0]).not.toBe(initList); // checking immutability
+});
+
+it('should memoized its utils methods', () => {
+  const initList = [1, 2, 3];
+  const { result } = setUp(initList);
+  const [, utils] = result.current;
+  const { set, clear, updateAt, remove, push, filter, sort, reset } = utils;
+
+  act(() => {
+    push(4);
+  });
+
+  expect(result.current[1]).toBe(utils);
+  expect(result.current[1].set).toBe(set);
+  expect(result.current[1].clear).toBe(clear);
+  expect(result.current[1].updateAt).toBe(updateAt);
+  expect(result.current[1].remove).toBe(remove);
+  expect(result.current[1].push).toBe(push);
+  expect(result.current[1].filter).toBe(filter);
+  expect(result.current[1].sort).toBe(sort);
+  expect(result.current[1].reset).toBe(reset);
+});