Skip to content

Commit b54faf7

Browse files
authored
Fix useAtomSuspense registry isolation (#420)
1 parent 5190908 commit b54faf7

4 files changed

Lines changed: 57 additions & 3 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@effect-atom/atom-react": patch
3+
---
4+
5+
Scope suspense promises to their registry.

packages/atom-react/src/Hooks.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ export const useAtom = <R, W, const Mode extends "value" | "promise" | "promiseE
209209
const atomPromiseMap = globalValue(
210210
"@effect-atom/atom-react/atomPromiseMap",
211211
() => ({
212-
suspendOnWaiting: new Map<Atom.Atom<any>, Promise<void>>(),
213-
default: new Map<Atom.Atom<any>, Promise<void>>()
212+
suspendOnWaiting: new WeakMap<Registry.Registry, WeakMap<Atom.Atom<any>, Promise<void>>>(),
213+
default: new WeakMap<Registry.Registry, WeakMap<Atom.Atom<any>, Promise<void>>>()
214214
})
215215
)
216216

@@ -219,7 +219,12 @@ function atomToPromise<A, E>(
219219
atom: Atom.Atom<Result.Result<A, E>>,
220220
suspendOnWaiting: boolean
221221
) {
222-
const map = suspendOnWaiting ? atomPromiseMap.suspendOnWaiting : atomPromiseMap.default
222+
const registries = suspendOnWaiting ? atomPromiseMap.suspendOnWaiting : atomPromiseMap.default
223+
let map = registries.get(registry)
224+
if (map === undefined) {
225+
map = new WeakMap()
226+
registries.set(registry, map)
227+
}
223228
let promise = map.get(atom)
224229
if (promise !== undefined) {
225230
return promise

packages/atom-react/test/index.test.tsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,49 @@ describe("atom-react", () => {
128128

129129
expect(screen.getByTestId("loading")).toBeInTheDocument()
130130
})
131+
132+
test("suspense subscriptions are isolated per registry", async () => {
133+
const atom = Atom.make<Result.Result<string>>(Result.initial())
134+
const firstRegistry = Registry.make()
135+
const secondRegistry = Registry.make()
136+
137+
function TestComponent({ id }: { readonly id: string }) {
138+
const value = useAtomSuspense(atom).value
139+
return <div data-testid={`${id}-value`}>{value}</div>
140+
}
141+
142+
render(
143+
<RegistryContext.Provider value={firstRegistry}>
144+
<Suspense fallback={<div data-testid="first-loading">Loading...</div>}>
145+
<TestComponent id="first" />
146+
</Suspense>
147+
</RegistryContext.Provider>
148+
)
149+
render(
150+
<RegistryContext.Provider value={secondRegistry}>
151+
<Suspense fallback={<div data-testid="second-loading">Loading...</div>}>
152+
<TestComponent id="second" />
153+
</Suspense>
154+
</RegistryContext.Provider>
155+
)
156+
157+
act(() => {
158+
secondRegistry.set(atom, Result.success("second"))
159+
})
160+
161+
await waitFor(() => {
162+
expect(screen.getByTestId("second-value")).toHaveTextContent("second")
163+
})
164+
expect(screen.getByTestId("first-loading")).toBeInTheDocument()
165+
166+
act(() => {
167+
firstRegistry.set(atom, Result.success("first"))
168+
})
169+
170+
await waitFor(() => {
171+
expect(screen.getByTestId("first-value")).toHaveTextContent("first")
172+
})
173+
})
131174
})
132175

133176
test("suspense error", () => {

tsconfig.base.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"strict": true,
66
"moduleDetection": "force",
77
"composite": true,
8+
"ignoreDeprecations": "6.0",
89
"downlevelIteration": true,
910
"resolveJsonModule": true,
1011
"esModuleInterop": false,

0 commit comments

Comments
 (0)