Skip to content

Commit d9f2fd2

Browse files
scamdendai-shi
andauthored
allow atom with default update function to return reset and add tests (#3137)
* allow atom with default update function to return reset and add tests * dont export type, and clarify contract of dynamic default with a few more tests --------- Co-authored-by: Daishi Kato <[email protected]>
1 parent 543f064 commit d9f2fd2

File tree

2 files changed

+100
-26
lines changed

2 files changed

+100
-26
lines changed
Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { atom } from '../../vanilla.ts'
2-
import type { SetStateAction, WritableAtom } from '../../vanilla.ts'
2+
import type { WritableAtom } from '../../vanilla.ts'
33
import { RESET } from './constants.ts'
44

55
type Read<Value, Args extends unknown[], Result> = WritableAtom<
@@ -8,38 +8,37 @@ type Read<Value, Args extends unknown[], Result> = WritableAtom<
88
Result
99
>['read']
1010

11+
type DefaultSetStateAction<Value> =
12+
| Value
13+
| typeof RESET
14+
| ((prev: Value) => Value | typeof RESET)
15+
1116
export function atomWithDefault<Value>(
12-
getDefault: Read<Value, [SetStateAction<Value> | typeof RESET], void>,
13-
): WritableAtom<Value, [SetStateAction<Value> | typeof RESET], void> {
17+
getDefault: Read<Value, [DefaultSetStateAction<Value>], void>,
18+
): WritableAtom<Value, [DefaultSetStateAction<Value>], void> {
1419
const EMPTY = Symbol()
1520
const overwrittenAtom = atom<Value | typeof EMPTY>(EMPTY)
1621

1722
if (import.meta.env?.MODE !== 'production') {
1823
overwrittenAtom.debugPrivate = true
1924
}
2025

21-
const anAtom: WritableAtom<
22-
Value,
23-
[SetStateAction<Value> | typeof RESET],
24-
void
25-
> = atom(
26-
(get, options) => {
27-
const overwritten = get(overwrittenAtom)
28-
if (overwritten !== EMPTY) {
29-
return overwritten
30-
}
31-
return getDefault(get, options)
32-
},
33-
(get, set, update) => {
34-
if (update === RESET) {
35-
set(overwrittenAtom, EMPTY)
36-
} else if (typeof update === 'function') {
37-
const prevValue = get(anAtom)
38-
set(overwrittenAtom, (update as (prev: Value) => Value)(prevValue))
39-
} else {
40-
set(overwrittenAtom, update)
41-
}
42-
},
43-
)
26+
const anAtom: WritableAtom<Value, [DefaultSetStateAction<Value>], void> =
27+
atom(
28+
(get, options) => {
29+
const overwritten = get(overwrittenAtom)
30+
if (overwritten !== EMPTY) {
31+
return overwritten
32+
}
33+
return getDefault(get, options)
34+
},
35+
(get, set, update) => {
36+
const newValue =
37+
typeof update === 'function'
38+
? (update as (prev: Value) => Value)(get(anAtom))
39+
: update
40+
set(overwrittenAtom, newValue === RESET ? EMPTY : newValue)
41+
},
42+
)
4443
return anAtom
4544
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
import { atom, createStore } from 'jotai/vanilla'
3+
import { RESET, atomWithDefault } from 'jotai/vanilla/utils'
4+
5+
describe('atomWithDefault', () => {
6+
beforeEach(() => {
7+
vi.clearAllMocks()
8+
})
9+
10+
it('should reset to initial value using RESET', () => {
11+
const initialValue = 10
12+
const testAtom = atomWithDefault(() => initialValue)
13+
const store = createStore()
14+
store.set(testAtom, 123)
15+
store.set(testAtom, RESET)
16+
expect(store.get(testAtom)).toBe(initialValue)
17+
})
18+
19+
it('should reset to initial value derived from another atom', () => {
20+
const initialValueAtom = atom(10)
21+
const testAtom = atomWithDefault((get) => get(initialValueAtom))
22+
const store = createStore()
23+
expect(store.get(testAtom)).toBe(10)
24+
store.set(testAtom, 123)
25+
store.set(initialValueAtom, 20)
26+
store.set(testAtom, RESET)
27+
expect(store.get(testAtom)).toBe(20)
28+
})
29+
30+
it(`should reflect changes to the initial value atom when main atom hasn't been manually changed`, () => {
31+
const initialValueAtom = atom(10)
32+
const testAtom = atomWithDefault((get) => get(initialValueAtom))
33+
const store = createStore()
34+
store.set(initialValueAtom, 20)
35+
expect(store.get(testAtom)).toBe(20)
36+
})
37+
38+
it(`should reflect changes to the initial value atom when main atom has been manually changed but then RESET`, () => {
39+
const initialValueAtom = atom(10)
40+
const testAtom = atomWithDefault((get) => get(initialValueAtom))
41+
const store = createStore()
42+
store.set(testAtom, 123)
43+
// if this RESET were storing 10 rather than EMPTY the next set wouldn't have an effect
44+
store.set(testAtom, RESET)
45+
store.set(initialValueAtom, 20)
46+
expect(store.get(testAtom)).toBe(20)
47+
})
48+
49+
it('should update atom with a new value', () => {
50+
const initialValue = 10
51+
const testAtom = atomWithDefault(() => initialValue)
52+
const store = createStore()
53+
store.set(testAtom, 123)
54+
store.set(testAtom, 30)
55+
expect(store.get(testAtom)).toBe(30)
56+
})
57+
58+
it('should update atom using a function', () => {
59+
const initialValue = 10
60+
const testAtom = atomWithDefault(() => initialValue)
61+
const store = createStore()
62+
store.set(testAtom, 123)
63+
store.set(testAtom, (prev: number) => prev + 10)
64+
expect(store.get(testAtom)).toBe(133)
65+
})
66+
67+
it('should reset with a function', () => {
68+
const initialValue = 10
69+
const testAtom = atomWithDefault(() => initialValue)
70+
const store = createStore()
71+
store.set(testAtom, 123)
72+
store.set(testAtom, () => RESET)
73+
expect(store.get(testAtom)).toBe(initialValue)
74+
})
75+
})

0 commit comments

Comments
 (0)