Skip to content
Open
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
5 changes: 4 additions & 1 deletion packages/arbor-react/src/useArbor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Arbor,
ArborNode,
isDetached,
isNode,
isProxiable,
ScopedStore,
Expand Down Expand Up @@ -76,7 +77,9 @@ function useArborDeprecated<T extends object>(store: Store<T>): ArborNode<T> {
}

return store.subscribe(() => {
setState(store.state)
if (!isDetached(store.state)) {
setState(store.state)
}
})
}, [state, store])

Expand Down
4 changes: 2 additions & 2 deletions packages/arbor-react/tests/useArbor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,14 @@ describe("useArbor", () => {

const { result, unmount } = renderHook(() => useArbor(store))

const initialSstate = result.current
const initialState = result.current

act(() => {
result.current.count++
})

const nextState = result.current
expect(initialSstate).not.toBe(nextState)
expect(initialState).not.toBe(nextState)

unmount()

Expand Down
10 changes: 9 additions & 1 deletion packages/arbor-store/src/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,15 @@ export function isDetached<T extends object>(node: T): boolean {
// but an ancestor has been detached.'
if (!isNode(node)) return true

return !node.$tree.getLinkFor(node) && !node.$tree.getNodeFor<Node>(node)
const path = node.$tree.getPathFor(node)

if (!path) {
return true
}

return path.seeds.some(
(seed) => !node.$tree.getLinkFor(seed) && !node.$tree.getNodeFor<Node>(seed)
Copy link
Owner Author

Choose a reason for hiding this comment

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

Ensures a node is detached if:

  1. itself is detached;
  2. or any of its ancestors are detached (since the state is a Tree).

)
}

/**
Expand Down
30 changes: 27 additions & 3 deletions packages/arbor-store/tests/arbor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,8 @@ describe("Arbor", () => {

delete store.state.todos[0]

// todos[0] should still exist in the previous snapshot
expect(todos[0]).toBeDefined()
expect(unwrap(todos[0])).toBe(state.todos[0])
expect(todos).toEqual([{ text: "Walk the dogs" }])
expect(todos[0]).toEqual({ text: "Walk the dogs" })

expect(store.state).toEqual({ todos: [{ text: "Walk the dogs" }] })
expect(store.state.todos).toEqual([{ text: "Walk the dogs" }])
Expand Down Expand Up @@ -822,6 +821,31 @@ describe("Arbor", () => {
expect(subscriber2).not.toHaveBeenCalled()
})

it("does not trigger notifications when mutations are performed on nodes belonging to detached paths", () => {
const store = new Arbor({
todos: [
{ id: 1, done: false, author: { name: "Alice", age: 29 } },
{ id: 2, done: false, author: { name: "Bob", age: 30 } },
],
})

const todo0 = store.state.todos[0]
const alice = store.state.todos[0].author
const subscriber1 = vi.fn()
const subscriber2 = vi.fn()

delete store.state.todos[0]

store.subscribe(subscriber1)
store.subscribeTo(alice, subscriber2)
store.subscribeTo(todo0, subscriber2)

expect(() => (todo0.done = true)).toThrow(DetachedNodeError)
expect(() => alice.age++).toThrow(DetachedNodeError)
expect(subscriber1).not.toHaveBeenCalled()
expect(subscriber2).not.toHaveBeenCalled()
})

it("ignores assignments when new value is the current value", () => {
const alice = { name: "Alice" }
const store = new Arbor({
Expand Down
14 changes: 14 additions & 0 deletions packages/arbor-store/tests/utilities/isDetached.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,18 @@ describe("isDetached", () => {

expect(isDetached(node)).toBe(true)
})

it("returns true if the node belongs to a detached path", () => {
const store = new Arbor({
todos: [
{ id: 1, text: "Do the dishes", author: { name: "Alice" } },
{ id: 2, text: "Walk the dogs", author: { name: "Bob" } },
],
})

const alice = store.state.todos[0].author
delete store.state.todos[0]

expect(isDetached(alice)).toBe(true)
})
})