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

fix(utils/atomWithDefault): support refresh #537

Merged
merged 7 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .size-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,16 @@
}
},
"utils.js": {
"bundled": 12801,
"minified": 6322,
"gzipped": 2390,
"bundled": 12933,
"minified": 6369,
"gzipped": 2409,
"treeshaked": {
"rollup": {
"code": 28,
"import_statements": 28
},
"webpack": {
"code": 1280
"code": 1289
}
}
},
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@
"@rollup/plugin-babel": "^5.3.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"@rollup/plugin-typescript": "^8.2.1",
"@testing-library/react": "^11.2.7",
"@testing-library/react": "^12.0.0",
"@types/jest": "^26.0.23",
"@types/react": "^17.0.11",
"@types/react-dom": "^17.0.7",
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export { selectAtom } from './utils/selectAtom'
export { useAtomCallback } from './utils/useAtomCallback'
export { freezeAtom, freezeAtomCreator } from './utils/freezeAtom'
export { splitAtom } from './utils/splitAtom'
export { atomWithDefault } from './utils/atomWithDefault'
export { atomWithDefault, REFRESH } from './utils/atomWithDefault'
export { waitForAll } from './utils/waitForAll'
export { atomWithHash } from './utils/atomWithHash'
export { atomWithStorage } from './utils/atomWithStorage'
30 changes: 18 additions & 12 deletions src/utils/atomWithDefault.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,34 @@
import { atom } from 'jotai'
import type { Atom, PrimitiveAtom } from 'jotai'
import type { Atom, WritableAtom, SetStateAction } from 'jotai'

type Read<Value> = Atom<Value>['read']

export function atomWithDefault<Value>(
getDefault: Read<Value>
): PrimitiveAtom<Value> {
export const REFRESH = Symbol()

export function atomWithDefault<Value>(getDefault: Read<Value>) {
type Update = SetStateAction<Value> | typeof REFRESH
const EMPTY = Symbol()
const overwrittenAtom = atom<Value | typeof EMPTY>(EMPTY)
const anAtom: PrimitiveAtom<Value> = atom(
const anAtom: WritableAtom<Value, Update> = atom(
(get) => {
const overwritten = get(overwrittenAtom)
if (overwritten !== EMPTY) {
return overwritten
}
return getDefault(get)
},
(get, set, update) =>
set(
overwrittenAtom,
typeof update === 'function'
? (update as (prev: Value) => Value)(get(anAtom))
: update
)
(get, set, update) => {
if (update === REFRESH) {
set(overwrittenAtom, EMPTY)
} else {
set(
overwrittenAtom,
typeof update === 'function'
? (update as (prev: Value) => Value)(get(anAtom))
: update
)
}
}
)
return anAtom
}
95 changes: 94 additions & 1 deletion tests/utils/atomWithDefault.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Suspense } from 'react'
import { fireEvent, render } from '@testing-library/react'
import { atom, useAtom } from '../../src/index'
import { atomWithDefault } from '../../src/utils'
import { atomWithDefault, REFRESH } from '../../src/utils'
import { getTestProvider } from '../testUtils'

const Provider = getTestProvider()
Expand Down Expand Up @@ -84,3 +84,96 @@ it('simple async get default', async () => {
fireEvent.click(getByText('button1'))
await findByText('count1: 3, count2: 5')
})

it('refresh sync atoms to default values', async () => {
const count1Atom = atom(1)
const count2Atom = atomWithDefault((get) => get(count1Atom) * 2)

const Counter: React.FC = () => {
const [count1, setCount1] = useAtom(count1Atom)
const [count2, setCount2] = useAtom(count2Atom)
return (
<>
<div>
count1: {count1}, count2: {count2}
</div>
<button onClick={() => setCount1((c) => c + 1)}>button1</button>
<button onClick={() => setCount2((c) => c + 1)}>button2</button>
<button onClick={() => setCount2(REFRESH)}>Refresh count2</button>
</>
)
}

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

await findByText('count1: 1, count2: 2')

fireEvent.click(getByText('button1'))
await findByText('count1: 2, count2: 4')

fireEvent.click(getByText('button2'))
await findByText('count1: 2, count2: 5')

fireEvent.click(getByText('button1'))
await findByText('count1: 3, count2: 5')

fireEvent.click(getByText('Refresh count2'))
await findByText('count1: 3, count2: 6')

fireEvent.click(getByText('button1'))
await findByText('count1: 4, count2: 8')
})

it('refresh async atoms to default values', async () => {
const count1Atom = atom(1)
const count2Atom = atomWithDefault(async (get) => {
await new Promise((r) => setTimeout(r, 10))
return get(count1Atom) * 2
})

const Counter: React.FC = () => {
const [count1, setCount1] = useAtom(count1Atom)
const [count2, setCount2] = useAtom(count2Atom)
return (
<>
<div>
count1: {count1}, count2: {count2}
</div>
<button onClick={() => setCount1((c) => c + 1)}>button1</button>
<button onClick={() => setCount2((c) => c + 1)}>button2</button>
<button onClick={() => setCount2(REFRESH)}>Refresh count2</button>
</>
)
}

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

await findByText('loading')
await findByText('count1: 1, count2: 2')

fireEvent.click(getByText('button1'))
await findByText('loading')
await findByText('count1: 2, count2: 4')

fireEvent.click(getByText('button2'))
await findByText('count1: 2, count2: 5')

fireEvent.click(getByText('button1'))
await findByText('count1: 3, count2: 5')

fireEvent.click(getByText('Refresh count2'))
await findByText('count1: 3, count2: 6')

fireEvent.click(getByText('button1'))
await findByText('count1: 4, count2: 8')
})
6 changes: 3 additions & 3 deletions tests/utils/waitForAll.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ const Provider = getTestProvider()
const consoleWarn = console.warn
const consoleError = console.error
beforeEach(() => {
jest.useFakeTimers()
console.warn = jest.fn()
console.error = jest.fn()
})
afterEach(() => {
jest.runOnlyPendingTimers()
jest.useRealTimers()
console.warn = consoleWarn
console.error = consoleError
})

// FIXME this seems to break in jest 27
// jest.useFakeTimers()

class ErrorBoundary extends React.Component<
{ message?: string },
{ hasError: boolean }
Expand Down
20 changes: 10 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1269,10 +1269,10 @@
dependencies:
"@sinonjs/commons" "^1.7.0"

"@testing-library/dom@^7.28.1":
version "7.31.2"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a"
integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ==
"@testing-library/dom@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.0.0.tgz#2bb994393c566aae021db86dd263ba06e8b71b38"
integrity sha512-Ym375MTOpfszlagRnTMO+FOfTt6gRrWiDOWmEnWLu9OvwCPOWtK6i5pBHmZ07wUJiQ7wWz0t8+ZBK2wFo2tlew==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
Expand All @@ -1281,15 +1281,15 @@
chalk "^4.1.0"
dom-accessibility-api "^0.5.6"
lz-string "^1.4.4"
pretty-format "^26.6.2"
pretty-format "^27.0.2"

"@testing-library/react@^11.2.7":
version "11.2.7"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818"
integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA==
"@testing-library/react@^12.0.0":
version "12.0.0"
resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.0.0.tgz#9aeb2264521522ab9b68f519eaf15136148f164a"
integrity sha512-sh3jhFgEshFyJ/0IxGltRhwZv2kFKfJ3fN1vTZ6hhMXzz9ZbbcTgmDYM4e+zJv+oiVKKEWZPyqPAh4MQBI65gA==
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^7.28.1"
"@testing-library/dom" "^8.0.0"

"@tootallnate/once@1":
version "1.1.2"
Expand Down