Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: avoid infinite loop with React 19 and StrictMode #2933

Merged
merged 11 commits into from
Jan 31, 2025
34 changes: 34 additions & 0 deletions tests/react/async2.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -380,3 +380,37 @@ describe('write to async atom twice', async () => {
await screen.findByText('count: 4')
})
})

describe('with onMount', () => {
it('does not infinite loop with setting a promise (#2931)', async () => {
const firstPromise = Promise.resolve(1)
const secondPromise = Promise.resolve(2)
const asyncAtom = atom(firstPromise)
asyncAtom.onMount = (setCount) => {
setCount((prev) => (prev === firstPromise ? secondPromise : prev))
}
const Component = () => {
const [count, setCount] = useAtom(asyncAtom)
return (
<>
<div>count: {count}</div>
<button onClick={() => setCount(async (c) => (await c) + 1)}>
button
</button>
</>
)
}
await act(async () => {
render(
<StrictMode>
<Suspense fallback="loading">
<Component />
</Suspense>
</StrictMode>,
)
})
await screen.findByText('count: 2')
await userEvent.click(screen.getByText('button'))
await screen.findByText('count: 3')
})
})
48 changes: 48 additions & 0 deletions tests/react/vanilla-utils/atomWithStorage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -745,3 +745,51 @@ describe('with subscribe method in string storage', () => {
// expect(storageData.count).toBe('11')
})
})

describe('with custom async storage', () => {
it('does not infinite loop (#2931)', async () => {
let storedValue = 0
let cachedPromise:
| [typeof storedValue, Promise<typeof storedValue>]
| null = null
const counterAtom = atomWithStorage('counter', 0, {
getItem(_key: string, _initialValue: number) {
if (cachedPromise && cachedPromise[0] === storedValue) {
return cachedPromise[1]
}
const promise = Promise.resolve(storedValue)
cachedPromise = [storedValue, promise]
return promise
},
async setItem(_key, newValue) {
storedValue = await new Promise((resolve) => resolve(newValue))
},
async removeItem() {},
})
const Component = () => {
const [count, setCount] = useAtom(counterAtom)
return (
<>
<div>count: {count}</div>
<button onClick={() => setCount(async (c) => (await c) + 1)}>
button
</button>
</>
)
}
await act(async () => {
render(
<StrictMode>
<Suspense fallback="loading">
<Component />
</Suspense>
</StrictMode>,
)
})
await screen.findByText('count: 0')
await userEvent.click(screen.getByText('button'))
await screen.findByText('count: 1')
await userEvent.click(screen.getByText('button'))
await screen.findByText('count: 2')
})
})