Skip to content

Commit

Permalink
fix(core): useAtom should return correct values when changing atoms (#…
Browse files Browse the repository at this point in the history
…1236)

* add a failing test

* fix useAtom when changing an atom

* minor renaming
  • Loading branch information
dai-shi committed Jun 29, 2022
1 parent dfcf2c7 commit fc1d878
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 27 deletions.
55 changes: 29 additions & 26 deletions src/core/useAtomValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,34 +40,37 @@ export function useAtomValue<Value>(
)

// Pull the atoms's state from the store into React state.
const [[version, value, atomFromUseReducer], rerenderIfChanged] = useReducer<
Reducer<
readonly [VersionObject | undefined, Awaited<Value>, Atom<Value>],
VersionObject | undefined
>,
undefined
>(
useCallback(
(prev, nextVersion) => {
const nextValue = getAtomValue(nextVersion)
if (Object.is(prev[1], nextValue) && prev[2] === atom) {
return prev // bail out
}
return [nextVersion, nextValue, atom]
},
[getAtomValue, atom]
),
undefined,
() => {
// NOTE should/could branch on mount?
const initialVersion = undefined
const initialValue = getAtomValue(initialVersion)
return [initialVersion, initialValue, atom]
}
)
const [[version, valueFromReducer, atomFromReducer], rerenderIfChanged] =
useReducer<
Reducer<
readonly [VersionObject | undefined, Awaited<Value>, Atom<Value>],
VersionObject | undefined
>,
undefined
>(
useCallback(
(prev, nextVersion) => {
const nextValue = getAtomValue(nextVersion)
if (Object.is(prev[1], nextValue) && prev[2] === atom) {
return prev // bail out
}
return [nextVersion, nextValue, atom]
},
[getAtomValue, atom]
),
undefined,
() => {
// NOTE should/could branch on mount?
const initialVersion = undefined
const initialValue = getAtomValue(initialVersion)
return [initialVersion, initialValue, atom]
}
)

if (atomFromUseReducer !== atom) {
let value = valueFromReducer
if (atomFromReducer !== atom) {
rerenderIfChanged(undefined)
value = getAtomValue()
}

useEffect(() => {
Expand Down
32 changes: 31 additions & 1 deletion tests/basic.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { fireEvent, render, waitFor } from '@testing-library/react'
import ReactDOM, { unstable_batchedUpdates } from 'react-dom'
import { atom, useAtom } from 'jotai'
import type { WritableAtom } from 'jotai'
import type { PrimitiveAtom, WritableAtom } from 'jotai'
import { getTestProvider } from './testUtils'

const Provider = getTestProvider()
Expand Down Expand Up @@ -1021,3 +1021,33 @@ it('onMount is not called when atom value is accessed from writeGetter in derive
expect(onMount).not.toBeCalled()
expect(onUnmount).not.toBeCalled()
})

it('useAtom returns consistent value with input with changing atoms (#1235)', async () => {
const countAtom = atom(0)
const valueAtoms = [atom(0), atom(1)]

const Counter = () => {
const [count, setCount] = useAtom(countAtom)
const [value] = useAtom(valueAtoms[count] as PrimitiveAtom<number>)
if (count !== value) {
throw new Error('value mismatch')
}
return (
<>
<div>count: {count}</div>
<button onClick={() => setCount((c) => c + 1)}>button</button>
</>
)
}

const { getByText, findByText } = render(
<Provider>
<Counter />
</Provider>
)

await findByText('count: 0')

fireEvent.click(getByText('button'))
await findByText('count: 1')
})

1 comment on commit fc1d878

@vercel
Copy link

@vercel vercel bot commented on fc1d878 Jun 29, 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.