Repository: apollographql/apollo-client

Problem statement:
When React.StrictMode wraps ApolloProvider or is in the same component as ApolloProvider. Using useEffect with async await and reactive variables will not work.
**Intended outcome:**

For [example](https://codesandbox.io/s/wispy-cherry-e1t9d?file=/src/index.js), click good button, the console output look like this.

![image](https://user-images.githubusercontent.com/60224604/102932668-132ed280-44e4-11eb-90a6-84b6fa8da106.png)

**Actual outcome:**

Refresh browser and click bad button, console outpu look like this.

![image](https://user-images.githubusercontent.com/60224604/102932789-48d3bb80-44e4-11eb-94fa-e0bbb43ef92b.png)

If move React.StrictMode to App.js, it will work.

↓ is bad too.

```tsx
  <ApolloProvider client={client}>
    <StrictMode>
      <App />
    </StrictMode>
  </ApolloProvider>,
```

**How to reproduce the issue:**

https://codesandbox.io/s/wispy-cherry-e1t9d?file=/src/index.js

**Versions**



Hints:

Patch:
diff --git a/src/react/hooks/useReactiveVar.ts b/src/react/hooks/useReactiveVar.ts
--- a/src/react/hooks/useReactiveVar.ts
+++ b/src/react/hooks/useReactiveVar.ts
@@ -5,11 +5,18 @@ export function useReactiveVar<T>(rv: ReactiveVar<T>): T {
   const value = rv();
   // We don't actually care what useState thinks the value of the variable
   // is, so we take only the update function from the returned array.
-  const mute = rv.onNextChange(useState(value)[1]);
+  const [, setValue] = useState(value);
+  // We subscribe to variable updates on initial mount and when the value has
+  // changed. This avoids a subtle bug in React.StrictMode where multiple listeners
+  // are added, leading to inconsistent updates.
+  useEffect(() => rv.onNextChange(setValue), [value]);
   // Once the component is unmounted, ignore future updates. Note that the
-  // useEffect function returns the mute function without calling it,
+  // above useEffect function returns a mute function without calling it,
   // allowing it to be called when the component unmounts. This is
-  // equivalent to useEffect(() => () => mute(), []), but shorter.
-  useEffect(() => mute, []);
+  // equivalent to the following, but shorter:
+  // useEffect(() => {
+  //   const mute = rv.onNextChange(setValue);
+  //   return () => mute();
+  // }, [value])
   return value;
 }


Test patch:
diff --git a/src/react/hooks/__tests__/useReactiveVar.test.tsx b/src/react/hooks/__tests__/useReactiveVar.test.tsx
--- a/src/react/hooks/__tests__/useReactiveVar.test.tsx
+++ b/src/react/hooks/__tests__/useReactiveVar.test.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useEffect } from "react";
 import { render, wait, act } from "@testing-library/react";
 
 import { itAsync } from "../../../testing";
@@ -13,25 +13,23 @@ describe("useReactiveVar Hook", () => {
     function Component() {
       const count = useReactiveVar(counterVar);
 
-      switch (++renderCount) {
-        case 1:
-          expect(count).toBe(0);
-          act(() => {
+      useEffect(() => {
+        switch (++renderCount) {
+          case 1:
+            expect(count).toBe(0);
             counterVar(count + 1);
-          });
-          break;
-        case 2:
-          expect(count).toBe(1);
-          act(() => {
+            break;
+          case 2:
+            expect(count).toBe(1);
             counterVar(counterVar() + 2);
-          });
-          break;
-        case 3:
-          expect(count).toBe(3);
-          break;
-        default:
-          reject(`too many (${renderCount}) renders`);
-      }
+            break;
+          case 3:
+            expect(count).toBe(3);
+            break;
+          default:
+            reject(`too many (${renderCount}) renders`);
+        }
+      });
 
       return null;
     }
@@ -129,20 +127,13 @@ describe("useReactiveVar Hook", () => {
     function Component() {
       const count = useReactiveVar(counterVar);
 
-      switch (++renderCount) {
-        case 1:
-          expect(count).toBe(0);
-          act(() => {
-            counterVar(count + 1);
-          });
-          break;
-        case 2:
-          expect(count).toBe(1);
-          act(() => {
-            counterVar(counterVar() + 2);
-          });
-          break;
-        case 3:
+      useEffect(() => {
+        if (count < 3) {
+          expect(count).toBe(renderCount++);
+          counterVar(count + 1);
+        }
+
+        if (count === 3) {
           expect(count).toBe(3);
           setTimeout(() => {
             unmount();
@@ -151,10 +142,8 @@ describe("useReactiveVar Hook", () => {
               attemptedUpdateAfterUnmount = true;
             }, 10);
           }, 10);
-          break;
-        default:
-          reject(`too many (${renderCount}) renders`);
-      }
+        }
+      });
 
       return null;
     }