Repository: pmndrs/valtio
Pull request #19 (2020-11-21 04:32:52)

feat: batch updates
This batches several updates into one. This changes the update behavior in timing.

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

Latest deployment of this branch, based on commit ffa1bac517d123091a58703c530abd6b7969d823:

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

dai-shi: This might raise other issues, but let's see how it goes. Please file an issue if anyone finds any.

Patch:
diff --git a/.size-snapshot.json b/.size-snapshot.json
index ec79504..7ebb15d 100644
--- a/.size-snapshot.json
+++ b/.size-snapshot.json
@@ -1,8 +1,8 @@
 {
   "index.js": {
-    "bundled": 5135,
-    "minified": 2272,
-    "gzipped": 962,
+    "bundled": 5312,
+    "minified": 2315,
+    "gzipped": 984,
     "treeshaked": {
       "rollup": {
         "code": 61,
@@ -14,19 +14,19 @@
     }
   },
   "index.cjs.js": {
-    "bundled": 5844,
-    "minified": 2695,
-    "gzipped": 1067
+    "bundled": 6035,
+    "minified": 2746,
+    "gzipped": 1086
   },
   "index.iife.js": {
-    "bundled": 6214,
-    "minified": 2022,
-    "gzipped": 927
+    "bundled": 6417,
+    "minified": 2073,
+    "gzipped": 952
   },
   "vanilla.js": {
-    "bundled": 3362,
-    "minified": 1463,
-    "gzipped": 676,
+    "bundled": 3539,
+    "minified": 1506,
+    "gzipped": 705,
     "treeshaked": {
       "rollup": {
         "code": 22,
@@ -38,8 +38,8 @@
     }
   },
   "vanilla.cjs.js": {
-    "bundled": 3821,
-    "minified": 1750,
-    "gzipped": 767
+    "bundled": 4012,
+    "minified": 1801,
+    "gzipped": 789
   }
 }
diff --git a/src/vanilla.ts b/src/vanilla.ts
index b3704f3..ffc0609 100644
--- a/src/vanilla.ts
+++ b/src/vanilla.ts
@@ -19,14 +19,20 @@ const snapshotCache = new WeakMap<
 
 export const proxy = <T extends object>(initialObject: T = {} as T): T => {
   let version = globalVersion
+  let pendingVersion = version
   const listeners = new Set<(nextVersion?: number) => void>()
   const notifyUpdate = (nextVersion?: number) => {
     if (!nextVersion) {
       nextVersion = ++globalVersion
     }
-    if (version !== nextVersion) {
-      version = nextVersion
-      listeners.forEach((listener) => listener(nextVersion))
+    if (nextVersion > pendingVersion) {
+      pendingVersion = nextVersion
+      Promise.resolve().then(() => {
+        if (pendingVersion > version) {
+          version = pendingVersion
+          listeners.forEach((listener) => listener(version))
+        }
+      })
     }
   }
   const emptyCopy = Array.isArray(initialObject)


Test patch:
diff --git a/tests/subscribe.test.tsx b/tests/subscribe.test.tsx
index 00681c3..5fde671 100644
--- a/tests/subscribe.test.tsx
+++ b/tests/subscribe.test.tsx
@@ -1,7 +1,7 @@
 import { proxy, subscribe } from '../src/index'
 
 describe('subscribe', () => {
-  it('should call subscription', () => {
+  it('should call subscription', async () => {
     const obj = proxy({ count: 0 })
     const handler = jest.fn()
 
@@ -9,10 +9,11 @@ describe('subscribe', () => {
 
     obj.count += 1
 
+    await Promise.resolve()
     expect(handler).toBeCalledTimes(1)
   })
 
-  it('should be able to unsubscribe', () => {
+  it('should be able to unsubscribe', async () => {
     const obj = proxy({ count: 0 })
     const handler = jest.fn()
 
@@ -21,10 +22,11 @@ describe('subscribe', () => {
 
     obj.count += 1
 
+    await Promise.resolve()
     expect(handler).toBeCalledTimes(0)
   })
 
-  it.skip('should call subscription of property', () => {
+  it.skip('should call subscription of property', async () => {
     const obj = proxy({ count: 0 })
     const handler = jest.fn()
 
@@ -32,10 +34,11 @@ describe('subscribe', () => {
 
     obj.count += 1
 
+    await Promise.resolve()
     expect(handler).toBeCalledTimes(1)
   })
 
-  it('should call subscription of nested property', () => {
+  it('should call subscription of nested property', async () => {
     const obj = proxy({ nested: { count: 0 } })
     const handler = jest.fn()
 
@@ -43,10 +46,11 @@ describe('subscribe', () => {
 
     obj.nested.count += 1
 
+    await Promise.resolve()
     expect(handler).toBeCalledTimes(1)
   })
 
-  it.skip('should not re-run subscription if no change', () => {
+  it.skip('should not re-run subscription if no change', async () => {
     const obj = proxy({ count: 0 })
     const handler = jest.fn()
 
@@ -54,10 +58,11 @@ describe('subscribe', () => {
 
     obj.count = 0
 
+    await Promise.resolve()
     expect(handler).toBeCalledTimes(0)
   })
 
-  it('should not cause infinite loop', () => {
+  it('should not cause infinite loop', async () => {
     const obj = proxy({ count: 0 })
     const handler = () => {
       // Reset count if above 5
@@ -71,7 +76,7 @@ describe('subscribe', () => {
     obj.count = 10
   })
 
-  it.skip('should not cause infinite loop with increment', () => {
+  it.skip('should not cause infinite loop with increment', async () => {
     const obj = proxy({ count: 0 })
     const handler = () => {
       obj.count += 1
@@ -81,4 +86,17 @@ describe('subscribe', () => {
 
     obj.count += 1
   })
+
+  it('should batch updates', async () => {
+    const obj = proxy({ count1: 0, count2: 0 })
+    const handler = jest.fn()
+
+    subscribe(obj, handler)
+
+    obj.count1 += 1
+    obj.count2 += 1
+
+    await Promise.resolve()
+    expect(handler).toBeCalledTimes(1)
+  })
 })