Repository: pmndrs/valtio
Pull request #99 (2021-02-28 01:04:04)

fix(computed): add warning to avoid overwriting property
fix https://github.com/pmndrs/eslint-plugin-valtio/issues/5

Extracted a fix in #94.

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/99/builds/105563) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit e29038a20f959761f21bf13d655ed384fd64dc74:

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


Patch:
diff --git a/src/utils.ts b/src/utils.ts
index 34aa682..dfc39d7 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -156,6 +156,9 @@ export const proxyWithComputed = <T extends object, U extends object>(
   const NOTIFIER = Symbol()
   Object.defineProperty(initialObject, NOTIFIER, { value: 0 })
   ;(Object.keys(computedFns) as (keyof U)[]).forEach((key) => {
+    if (Object.getOwnPropertyDescriptor(initialObject, key)) {
+      throw new Error('object property already defined')
+    }
     const computedFn = computedFns[key]
     const { get, set } = (typeof computedFn === 'function'
       ? { get: computedFn }
diff --git a/src/vanilla.ts b/src/vanilla.ts
index b88fa3b..64f72dd 100644
--- a/src/vanilla.ts
+++ b/src/vanilla.ts
@@ -129,7 +129,11 @@ export const proxy = <T extends object>(initialObject: T = {} as T): T => {
       if (childListeners) {
         childListeners.delete(notifyUpdate)
       }
-      if (refSet.has(value) || !isSupportedObject(value)) {
+      if (
+        refSet.has(value) ||
+        !isSupportedObject(value) ||
+        Object.getOwnPropertyDescriptor(target, prop)?.set
+      ) {
         target[prop] = value
       } else if (value instanceof Promise) {
         target[prop] = value
@@ -160,7 +164,7 @@ export const proxy = <T extends object>(initialObject: T = {} as T): T => {
       initialObject,
       key
     ) as PropertyDescriptor
-    if (desc.get) {
+    if (desc.get || desc.set) {
       Object.defineProperty(baseObject, key, desc)
     } else {
       proxyObject[key] = (initialObject as any)[key]


Test patch:
diff --git a/tests/computed.test.tsx b/tests/computed.test.tsx
index acf5b5c..ed4bfd7 100644
--- a/tests/computed.test.tsx
+++ b/tests/computed.test.tsx
@@ -110,3 +110,43 @@ it('computed getters and setters', async () => {
   expect(snapshot(state)).toMatchObject({ text: 'a', count: 0.5, doubled: 1 })
   expect(computeDouble).toBeCalledTimes(3)
 })
+
+it('computed setters with object and array', async () => {
+  const state = proxyWithComputed(
+    {
+      obj: { a: 1 },
+      arr: [2],
+    },
+    {
+      object: {
+        get: (snap) => snap.obj,
+        set: (state, newValue: any) => {
+          state.obj = newValue
+        },
+      },
+      array: {
+        get: (snap) => snap.arr,
+        set: (state, newValue: any) => {
+          state.arr = newValue
+        },
+      },
+    }
+  )
+
+  expect(snapshot(state)).toMatchObject({
+    obj: { a: 1 },
+    arr: [2],
+    object: { a: 1 },
+    array: [2],
+  })
+
+  state.object = { a: 2 }
+  state.array = [3]
+  await Promise.resolve()
+  expect(snapshot(state)).toMatchObject({
+    obj: { a: 2 },
+    arr: [3],
+    object: { a: 2 },
+    array: [3],
+  })
+})