Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions src/vanilla/internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ const recomputeInvalidatedAtoms: RecomputeInvalidatedAtoms = (store) => {
const mountedMap = buildingBlocks[1]
const invalidatedAtoms = buildingBlocks[2]
const changedAtoms = buildingBlocks[3]
const storeHooks = buildingBlocks[6]
const ensureAtomState = buildingBlocks[11]
const readAtomState = buildingBlocks[14]
const mountDependencies = buildingBlocks[17]
Expand All @@ -465,7 +466,11 @@ const recomputeInvalidatedAtoms: RecomputeInvalidatedAtoms = (store) => {
// This is a topological sort via depth-first search, slightly modified from
// what's described here for simplicity and performance reasons:
// https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
const topSortedReversed: [atom: AnyAtom, atomState: AtomState][] = []
const topSortedReversed: [
atom: AnyAtom,
atomState: AtomState,
epochNumber: EpochNumber,
][] = []
const visiting = new WeakSet<AnyAtom>()
const visited = new WeakSet<AnyAtom>()
// Visit the root atom. This is the only atom in the dependency graph
Expand All @@ -484,7 +489,7 @@ const recomputeInvalidatedAtoms: RecomputeInvalidatedAtoms = (store) => {
// performance, we will simply push onto the end, and then will iterate in
// reverse order later.
if (invalidatedAtoms.get(a) === aState.n) {
topSortedReversed.push([a, aState])
topSortedReversed.push([a, aState, aState.n])
} else if (
import.meta.env?.MODE !== 'production' &&
invalidatedAtoms.has(a)
Expand All @@ -507,7 +512,7 @@ const recomputeInvalidatedAtoms: RecomputeInvalidatedAtoms = (store) => {
// Step 2: use the topSortedReversed atom list to recompute all affected atoms
// Track what's changed, so that we can short circuit when possible
for (let i = topSortedReversed.length - 1; i >= 0; --i) {
const [a, aState] = topSortedReversed[i]!
const [a, aState, prevEpochNumber] = topSortedReversed[i]!
let hasChangedDeps = false
for (const dep of aState.d.keys()) {
if (dep !== a && changedAtoms.has(dep)) {
Expand All @@ -518,6 +523,10 @@ const recomputeInvalidatedAtoms: RecomputeInvalidatedAtoms = (store) => {
if (hasChangedDeps) {
readAtomState(store, a)
mountDependencies(store, a)
if (prevEpochNumber !== aState.n) {
changedAtoms.add(a)
storeHooks.c?.(a)
}
}
invalidatedAtoms.delete(a)
}
Expand Down
16 changes: 16 additions & 0 deletions tests/vanilla/store.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1270,3 +1270,19 @@ it('updates dependents when it eagerly recomputes dirty atoms', () => {

expect(store.get(activeCountAtom)).toBe(1)
})

it('triggers the sub when setting inside an atom read', () => {
const store = createStore()
const basicAtom = atom(0)
const previousValueAtom = atom()
const testAtom = atom((get) => {
const newValue = get(basicAtom)
store.set(previousValueAtom, newValue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to support this.
If we can't support this, we should probably warn it (dev only) if we can detect it.

const result = get(previousValueAtom)
return result
})
const spy = vi.fn()
store.sub(testAtom, spy)
store.set(basicAtom, 1)
expect(spy).toHaveBeenCalledTimes(1)
})
Loading