Repository: pmndrs/valtio
Pull request #35 (2020-11-30 07:40:52)

fix: object getters returns snapshot undefined
Issue reported on Twitter: https://twitter.com/dai_shi/status/1333202394809700354

> There seems to be a bug in handling object getters. If we return non-objects, it's not undefined.

codesandbox[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/pmndrs/valtio/pr/35/builds/78086) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 3c65cc19bef6269199ab2d91c252f50960f5e1ee:

|Sandbox| Source |
|--|--|
|[React](https://codesandbox.io/s/react-wr68s)| Configuration |
|[React Typescript](https://codesandbox.io/s/react-typescript-xhrr1)| Configuration |

itaditya: It's working! https://codesandbox.io/s/user-todos-with-valtio-check-fix-g7nuu?file=/src/App.js

Thanks @dai-shi
dai-shi: Thanks for checking. I will merge and release.

Patch:
diff --git a/.size-snapshot.json b/.size-snapshot.json
index d872b3e..2451c25 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,8 +1,8 @@
 {
   "index.js": {
-    "bundled": 6546,
-    "minified": 3224,
-    "gzipped": 1227,
+    "bundled": 6620,
+    "minified": 3242,
+    "gzipped": 1239,
     "treeshaked": {
       "rollup": {
         "code": 61,
@@ -14,19 +14,19 @@
     }
   },
   "index.cjs.js": {
-    "bundled": 7266,
-    "minified": 3636,
-    "gzipped": 1345
+    "bundled": 7341,
+    "minified": 3654,
+    "gzipped": 1354
   },
   "index.iife.js": {
-    "bundled": 7702,
-    "minified": 2876,
-    "gzipped": 1199
+    "bundled": 7781,
+    "minified": 2888,
+    "gzipped": 1207
   },
   "vanilla.js": {
-    "bundled": 4941,
-    "minified": 2550,
-    "gzipped": 945,
+    "bundled": 5015,
+    "minified": 2568,
+    "gzipped": 955,
     "treeshaked": {
       "rollup": {
         "code": 22,
@@ -38,8 +38,8 @@
     }
   },
   "vanilla.cjs.js": {
-    "bundled": 5397,
-    "minified": 2817,
-    "gzipped": 1054
+    "bundled": 5472,
+    "minified": 2835,
+    "gzipped": 1061
   }
 }
diff --git a/src/vanilla.ts b/src/vanilla.ts
index 67eb0e2..1b576bc 100644
--- a/src/vanilla.ts
+++ b/src/vanilla.ts
@@ -18,8 +18,10 @@ const isSupportedObject = (x: unknown): x is object =>
   !(x instanceof RegExp) &&
   !(x instanceof ArrayBuffer)
 
-const proxyCache = new WeakMap<object, object>()
-let globalVersion = 0
+type ProxyObject = object
+const proxyCache = new WeakMap<object, ProxyObject>()
+
+let globalVersion = 1
 const snapshotCache = new WeakMap<
   object,
   {
@@ -77,8 +79,10 @@ export const proxy = <T extends object>(initialObject: T = {} as T): T => {
                 throw value
               },
             })
-          } else {
+          } else if ((value as any)[VERSION]) {
             snapshot[key] = (value as any)[SNAPSHOT]
+          } else {
+            snapshot[key] = value
           }
         })
         if (


Test patch:
diff --git a/tests/getter.test.tsx b/tests/getter.test.tsx
new file mode 100644
index 0000000..d7679fa
--- /dev/null
+++ b/tests/getter.test.tsx
@@ -0,0 +1,87 @@
+import React, { StrictMode } from 'react'
+import { fireEvent, render, waitFor } from '@testing-library/react'
+import { proxy, useProxy } from '../src/index'
+
+it('simple object getters', async () => {
+  const computeDouble = jest.fn((x) => x * 2)
+  const state = proxy({
+    count: 0,
+    get doubled() {
+      return computeDouble(this.count)
+    },
+  })
+
+  const Counter: React.FC<{ name: string }> = ({ name }) => {
+    const snapshot = useProxy(state)
+    return (
+      <>
+        <div>
+          {name} count: {snapshot.doubled}
+        </div>
+        <button onClick={() => ++state.count}>{name} button</button>
+      </>
+    )
+  }
+
+  const { getByText } = render(
+    <StrictMode>
+      <Counter name="A" />
+      <Counter name="B" />
+    </StrictMode>
+  )
+
+  await waitFor(() => {
+    getByText('A count: 0')
+    getByText('B count: 0')
+  })
+
+  computeDouble.mockClear()
+  fireEvent.click(getByText('A button'))
+  await waitFor(() => {
+    getByText('A count: 2')
+    getByText('B count: 2')
+  })
+  expect(computeDouble).toBeCalledTimes(1)
+})
+
+it('object getters returning object', async () => {
+  const computeDouble = jest.fn((x) => x * 2)
+  const state = proxy({
+    count: 0,
+    get doubled() {
+      return { value: computeDouble(this.count) }
+    },
+  })
+
+  const Counter: React.FC<{ name: string }> = ({ name }) => {
+    const snapshot = useProxy(state)
+    return (
+      <>
+        <div>
+          {name} count: {snapshot.doubled.value}
+        </div>
+        <button onClick={() => ++state.count}>{name} button</button>
+      </>
+    )
+  }
+
+  const { getByText } = render(
+    <StrictMode>
+      <Counter name="A" />
+      <Counter name="B" />
+    </StrictMode>
+  )
+
+  await waitFor(() => {
+    getByText('A count: 0')
+    getByText('B count: 0')
+  })
+
+  computeDouble.mockClear()
+  fireEvent.click(getByText('A button'))
+  await waitFor(() => {
+    getByText('A count: 2')
+    getByText('B count: 2')
+  })
+  expect(computeDouble).toBeCalledTimes(1)
+})