Repository: streamich/react-use (ts)

Issue:
useMap(): outdated closure in `utils.get` method
**What is the current behavior?**

The value returned by the `get` is incorrect

https://github.com/streamich/react-use/blob/master/src/useMap.ts#L15
```
const utils = useMemo<Actions<T>>(
  () => ({
    // The reference to `map` is outdated
    get: key => map[key],
    // ...
  }),
  [set] // `map` is missing here in the dependency array.
);
```

Pull request:
fix(useMap): methods with side effects should be stable across renders.
# Description

Fixes #816 

Due a change in `useMap` fixing the `get` method using an outdated closure an error was introduced where the methods with side effects, i.e. `set`, `remove` and `reset` were no longer memoized properly and therefore not stable across renders. 

This PR memoizes the individual methods with side effects properly but *not* the entire `utils` object.

## Type of change

<!-- Check all relevant options. -->
- [x] Bug fix _(non-breaking change which fixes an issue)_
- [ ] New feature _(non-breaking change which adds functionality)_
- [ ] **Breaking change** _(fix or feature that would cause existing functionality to not work as before)_


Patch:
diff --git a/src/useMap.ts b/src/useMap.ts
index a0c60e96..236ac177 100644
--- a/src/useMap.ts
+++ b/src/useMap.ts
@@ -1,18 +1,20 @@
-import { useState, useMemo } from 'react';
+import { useState, useMemo, useCallback } from 'react';
 
-export interface Actions<T extends object> {
-  get: <K extends keyof T>(key: K) => T[K];
+export interface StableActions<T extends object> {
   set: <K extends keyof T>(key: K, value: T[K]) => void;
   remove: <K extends keyof T>(key: K) => void;
   reset: () => void;
 }
 
+export interface Actions<T extends object> extends StableActions<T> {
+  get: <K extends keyof T>(key: K) => T[K];
+}
+
 const useMap = <T extends object = any>(initialMap: T = {} as T): [T, Actions<T>] => {
   const [map, set] = useState<T>(initialMap);
 
-  const utils = useMemo<Actions<T>>(
+  const stableActions = useMemo<StableActions<T>>(
     () => ({
-      get: key => map[key],
       set: (key, entry) => {
         set(prevMap => ({
           ...prevMap,
@@ -27,9 +29,14 @@ const useMap = <T extends object = any>(initialMap: T = {} as T): [T, Actions<T>
       },
       reset: () => set(initialMap),
     }),
-    [map, set]
+    [set]
   );
 
+  const utils = {
+    get: useCallback(key => map[key], [map]),
+    ...stableActions,
+  } as Actions<T>;
+
   return [map, utils];
 };
 


Test patch:
diff --git a/tests/useMap.test.ts b/tests/useMap.test.ts
index 58623f6b..0bcf3e21 100644
--- a/tests/useMap.test.ts
+++ b/tests/useMap.test.ts
@@ -121,3 +121,17 @@ it('should reset map to initial object provided', () => {
 
   expect(result.current[0]).toEqual({ foo: 'bar', a: 1 });
 });
+
+it('should memoize actions with side effects', () => {
+  const { result } = setUp({ foo: 'bar', a: 1 });
+  const [, utils] = result.current;
+  const { set, remove, reset } = utils;
+
+  act(() => {
+    set('foo', 'baz');
+  });
+
+  expect(result.current[1].set).toBe(set);
+  expect(result.current[1].remove).toBe(remove);
+  expect(result.current[1].reset).toBe(reset);
+});