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

feat(util): atomWithDefault #379

Merged
merged 10 commits into from
Mar 27, 2021
16 changes: 16 additions & 0 deletions docs/api/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,22 @@ const countReducerAtom = atomWithReducer(0, countReducer)

https://codesandbox.io/s/react-typescript-forked-g3tsx

## atomWithDefault

Ref: https://github.com/pmndrs/jotai/issues/352

This is a function to create a primitive atom.
Its default value can be specified with a read function instead of a static initial value.

```js
import { atomWithDefault } from 'jotai/utils'

const count1Atom = atom(1)
const count2Atom = atomWithDefault((get) => get(count1Atom) * 2)
```

https://codesandbox.io/s/react-typescript-forked-unfro

## atomFamily

Ref: https://github.com/pmndrs/jotai/issues/23
Expand Down
1 change: 1 addition & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { selectAtom } from './utils/selectAtom'
export { useAtomCallback } from './utils/useAtomCallback'
export { freezeAtom, atomFrozenInDev } from './utils/freezeAtom'
export { splitAtom } from './utils/splitAtom'
export { atomWithDefault } from './utils/atomWithDefault'
26 changes: 26 additions & 0 deletions src/utils/atomWithDefault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { atom, PrimitiveAtom } from 'jotai'
import type { Read } from '../core/types'

export function atomWithDefault<Value>(
getDefault: Read<Value>
): PrimitiveAtom<Value> {
const overwrittenAtom = atom(false)
const anAtom: PrimitiveAtom<Value> = atom(
(get) => {
if (get(overwrittenAtom)) {
return get(anAtom)
}
return getDefault(get)
},
(get, set, update) => {
set(overwrittenAtom, true)
set(
anAtom,
typeof update === 'function'
? (update as (prev: Value) => Value)(get(anAtom))
: update
)
}
)
return anAtom
}
2 changes: 1 addition & 1 deletion src/utils/atomWithReset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const RESET = Symbol()

export function atomWithReset<Value>(initialValue: Value) {
type Update = SetStateAction<Value> | typeof RESET
const anAtom: any = atom<Value, Update>(initialValue, (get, set, update) => {
const anAtom = atom<Value, Update>(initialValue, (get, set, update) => {
if (update === RESET) {
set(anAtom, initialValue)
} else {
Expand Down
85 changes: 85 additions & 0 deletions tests/utils/atomWithDefault.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { Fragment, Suspense } from 'react'
import { fireEvent, render } from '@testing-library/react'
import { Provider as ProviderOrig, atom, useAtom } from '../../src/index'
import { atomWithDefault } from '../../src/utils'

const Provider = process.env.PROVIDER_LESS_MODE ? Fragment : ProviderOrig

it('simple sync get default', 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>
</>
)
}

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')
})

it('simple async get default', 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>
</>
)
}

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')
})