Skip to content

Commit

Permalink
fix(utils): resolve infinite loop with loadable of derived async atom…
Browse files Browse the repository at this point in the history
… in some cases (#1118)

* add failing test

* revert #1089 to fix infinite loop
  • Loading branch information
hirokibeta authored Apr 28, 2022
1 parent 975d4ed commit f08f345
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 51 deletions.
3 changes: 0 additions & 3 deletions src/core/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,9 +465,6 @@ export const createStore = (
): void => {
const atomState = getAtomState(version, atom)
if (atomState) {
if ('p' in atomState) {
cancelSuspensePromise(atomState.p)
}
const nextAtomState: AtomState<Value> = {
...atomState, // copy everything
i: atomState.r, // set invalidated revision
Expand Down
48 changes: 0 additions & 48 deletions tests/async.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -884,54 +884,6 @@ itSkipIfVersionedWrite('set two promise atoms at once', async () => {
await findByText('count: 3')
})

it('should override promise in derived atom (#1054)', async () => {
const countAtom = atom(0)
const derivedAtom = atom((get) => {
const count = get(countAtom)
if (count === 0) {
return new Promise<number>(() => {})
}
return count
})

const Counter = () => {
const [derived] = useAtom(derivedAtom)
return <div>derived: {derived}</div>
}

const Control = () => {
const [count, setCount] = useAtom(countAtom)
return (
<>
<div>count: {count}</div>
<button onClick={() => setCount((c) => c + 1)}>button</button>
</>
)
}

const { getByText } = render(
<StrictMode>
<Provider>
<Suspense fallback="loading">
<Counter />
</Suspense>
<Control />
</Provider>
</StrictMode>
)

await waitFor(() => {
getByText('loading')
getByText('count: 0')
})

fireEvent.click(getByText('button'))
await waitFor(() => {
getByText('derived: 1')
getByText('count: 1')
})
})

it('async write chain', async () => {
const countAtom = atom(0)
const asyncWriteAtom = atom(null, async (_get, set, _arg) => {
Expand Down
31 changes: 31 additions & 0 deletions tests/utils/loadable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,37 @@ it('loadable can use resolved promises syncronously', async () => {
expect(effectCallback).toHaveBeenLastCalledWith({ state: 'hasData', data: 5 })
})

it('loadable of a derived async atom does not trigger infinite loop (#1114)', async () => {
let resolveAsync!: (x: number) => void
const baseAtom = atom(0)
const asyncAtom = atom((get) => {
get(baseAtom)
return new Promise<number>((resolve) => (resolveAsync = resolve))
})

const Trigger = () => {
const trigger = useSetAtom(baseAtom)
return (
<>
<button onClick={() => trigger((value) => value)}>trigger</button>
</>
)
}

const { findByText, getByText } = render(
<Provider>
<Trigger />
<LoadableComponent asyncAtom={asyncAtom} />
</Provider>
)

getByText('Loading...')
fireEvent.click(getByText('trigger'))
await new Promise((r) => setTimeout(r, 10))
resolveAsync(5)
await findByText('Data: 5')
})

interface LoadableComponentProps {
asyncAtom: Atom<Promise<number> | Promise<string> | string | number>
effectCallback?: (loadableValue: any) => void
Expand Down

1 comment on commit f08f345

@vercel
Copy link

@vercel vercel bot commented on f08f345 Apr 28, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.