Skip to content

Commit 0d40bfc

Browse files
authored
fix: regression in #1176 (#1206)
1 parent 2622a27 commit 0d40bfc

2 files changed

Lines changed: 48 additions & 8 deletions

File tree

src/react.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -127,25 +127,21 @@ export function useSnapshot<T extends object>(
127127
[proxyObject],
128128
)
129129
const lastSnapshot = useRef<Snapshot<T>>(undefined)
130-
const subscribed = useRef(false)
130+
let inRender = true
131131
const currSnapshot = useSyncExternalStore(
132132
useCallback(
133133
(callback) => {
134-
subscribed.current = true
135134
const unsub = subscribe(proxyObject, callback, notifyInSync)
136135
callback() // Note: do we really need this?
137-
return () => {
138-
unsub()
139-
subscribed.current = false
140-
}
136+
return unsub
141137
},
142138
[proxyObject, notifyInSync],
143139
),
144140
() => {
145141
const nextSnapshot = snapshot(proxyObject)
146142
try {
147143
if (
148-
subscribed.current &&
144+
!inRender &&
149145
lastSnapshot.current &&
150146
!isChanged(
151147
lastSnapshot.current,
@@ -164,6 +160,9 @@ export function useSnapshot<T extends object>(
164160
},
165161
() => snapshot(proxyObject),
166162
)
163+
// TODO how could we bypass this?
164+
// eslint-disable-next-line react-hooks/immutability
165+
inRender = false
167166
useLayoutEffect(() => {
168167
lastSnapshot.current = currSnapshot
169168
})

tests/basic.test.tsx

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { StrictMode, useEffect, useState } from 'react'
1+
import { StrictMode, useEffect, useLayoutEffect, useState } from 'react'
22
import { act, fireEvent, render, screen } from '@testing-library/react'
33
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
44
import { proxy, snapshot, useSnapshot } from 'valtio'
@@ -399,6 +399,47 @@ describe('basic', () => {
399399
expect(screen.getByText('count: 1')).toBeInTheDocument()
400400
})
401401

402+
it('should not commit stale value for newly accessed keys on local rerender (regression in #1176)', async () => {
403+
const obj = proxy({ count: 0, anotherValue: 0 })
404+
405+
const commitFn = vi.fn()
406+
const Component = () => {
407+
const [showAnotherValue, setShowAnotherValue] = useState(false)
408+
const snap = useSnapshot(obj)
409+
const value = showAnotherValue ? snap.anotherValue : 'hidden'
410+
useLayoutEffect(() => {
411+
commitFn(value)
412+
}, [value])
413+
return (
414+
<>
415+
<div>count: {snap.count}</div>
416+
{showAnotherValue && <div>anotherValue: {snap.anotherValue}</div>}
417+
<button onClick={() => setShowAnotherValue(true)}>
418+
showAnotherValue
419+
</button>
420+
</>
421+
)
422+
}
423+
424+
render(<Component />)
425+
426+
expect(screen.getByText('count: 0')).toBeInTheDocument()
427+
expect(screen.queryByText('anotherValue: 0')).not.toBeInTheDocument()
428+
expect(screen.queryByText('anotherValue: 1')).not.toBeInTheDocument()
429+
expect(commitFn).toBeCalledTimes(1)
430+
expect(commitFn).toHaveBeenNthCalledWith(1, 'hidden')
431+
432+
obj.anotherValue += 1
433+
434+
await act(() => vi.advanceTimersByTimeAsync(0))
435+
expect(commitFn).toBeCalledTimes(1)
436+
437+
fireEvent.click(screen.getByText('showAnotherValue'))
438+
439+
expect(screen.getByText('anotherValue: 1')).toBeInTheDocument()
440+
expect(commitFn.mock.calls.map(([value]) => value)).toEqual(['hidden', 1])
441+
})
442+
402443
it('counter with sync option', async () => {
403444
const obj = proxy({ count: 0 })
404445

0 commit comments

Comments
 (0)