Skip to content

Commit

Permalink
Merge branch 'main' into flush-callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
dai-shi authored Jan 19, 2025
2 parents 90a204a + e98b58a commit b059877
Show file tree
Hide file tree
Showing 8 changed files with 90 additions and 28 deletions.
19 changes: 12 additions & 7 deletions docs/recipes/custom-useatom-hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ This page shows the ways of creating different utility functions. Utility functi
import { useAtomValue } from 'jotai'
import { selectAtom } from 'jotai/utils'

/* if an atom is created here, please use `useMemo(() => atom(initValue), [initValue])` instead. */
export function useSelectAtom(anAtom, keyFn) {
return useAtomValue(selectAtom(anAtom, keyFn))
export function useSelectAtom(anAtom, selector) {
const selectorAtom = selectAtom(
anAtom,
selector,
// Alternatively, you can customize `equalityFn` to determine when it will rerender
// Check selectAtom's signature for details.
)
return useAtomValue(selectorAtom)
}

// how to use it
useSelectAtom(
useMemo(() => atom(initValue), [initValue]),
useCallback((state) => state.prop, []),
)
function useN(n) {
const selector = useCallback((v) => v[n], [n])
return useSelectAtom(arrayAtom, selector)
}
```

Please note that in this case `keyFn` must be stable, either define outside render or wrap with `useCallback`.
Expand Down
8 changes: 4 additions & 4 deletions docs/recipes/large-objects.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ const People = () => {

> This function creates a derived atom whose value is a function of the original atom's value. [jotai/utils](../utilities/select.mdx)
This utility is like `focusAtom`, but we use it when we have a read-only atom to select part of it, and it always returns a read-only atom.
This utility is like `focusAtom`, but it always returns a read-only atom.

Assume we want to consume the info data, and its data is always unchangeable. We can make a read-only atom from it and select that created atom.

```js
// first we create a read-only atom based on initialData.info
const readOnlyInfoAtom = atom((get) => get(dataAtom).info)
// first we create a derived atom based on initialData.info
const infoAtom = atom((get) => get(dataAtom).info)
```

Then we use it in our component:
Expand All @@ -107,7 +107,7 @@ import { selectAtom, splitAtom } from 'jotai/utils'

const tagsSelector = (s) => s.tags
const Tags = () => {
const tagsAtom = selectAtom(readOnlyInfoAtom, tagsSelector)
const tagsAtom = selectAtom(infoAtom, tagsSelector)
const tagsAtomsAtom = splitAtom(tagsAtom)
const [tagAtoms] = useAtom(tagsAtomsAtom)
return (
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
"@types/node": "^22.10.2",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^2.1.8",
"@vitest/eslint-plugin": "^1.1.21",
"@vitest/ui": "^2.1.8",
Expand Down
50 changes: 47 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions tests/vanilla/basic.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ it('creates atoms', () => {
const decrementCountAtom = atom(null, (get, set) => {
set(countAtom, get(countAtom) - 1)
})
delete countAtom.debugLabel
delete doubledCountAtom.debugLabel
delete sumCountAtom.debugLabel
delete decrementCountAtom.debugLabel
expect({
countAtom,
doubledCountAtom,
Expand Down Expand Up @@ -54,6 +58,7 @@ it('creates atoms', () => {
it('should let users mark atoms as private', () => {
const internalAtom = atom(0)
internalAtom.debugPrivate = true
delete internalAtom.debugLabel

expect(internalAtom).toMatchInlineSnapshot(`
{
Expand Down
6 changes: 0 additions & 6 deletions tests/vanilla/store.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -755,16 +755,13 @@ describe('should mount and trigger listeners even when an error is thrown', () =
it('in synchronous write', () => {
const store = createStore()
const a = atom(0)
a.debugLabel = 'a'
const e = atom(() => {
throw new Error('error')
})
e.debugLabel = 'e'
const b = atom(null, (get, set) => {
set(a, 1)
get(e)
})
b.debugLabel = 'b'
const listener = vi.fn()
store.sub(a, listener)
try {
Expand Down Expand Up @@ -1104,14 +1101,11 @@ it('should pass store and atomState to the atom initializer', () => {

it('recomputes dependents of unmounted atoms', () => {
const a = atom(0)
a.debugLabel = 'a'
const bRead = vi.fn((get: Getter) => {
return get(a)
})
const b = atom(bRead)
b.debugLabel = 'b'
const c = atom((get) => get(b))
c.debugLabel = 'c'
const w = atom(null, (get, set) => {
set(a, 1)
get(c)
Expand Down
13 changes: 6 additions & 7 deletions tests/vanilla/storedev.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ describe('[DEV-ONLY] dev-only methods rev4', () => {
throw new Error('dev methods are not available')
}
const countAtom = atom(0)
countAtom.debugLabel = 'countAtom'
store.set(countAtom, 1)
const weakMap = store.dev4_get_internal_weak_map()
expect(weakMap.get(countAtom)?.v).toEqual(1)
Expand Down Expand Up @@ -60,23 +59,24 @@ describe('[DEV-ONLY] dev-only methods rev4', () => {
throw new Error('dev methods are not available')
}
const countAtom = atom(0)
countAtom.debugLabel = 'countAtom'
const derivedAtom = atom((get) => get(countAtom) * 2)
const unsub = store.sub(derivedAtom, vi.fn())
store.set(countAtom, 1)
const result = store.dev4_get_mounted_atoms()
expect(
Array.from(result).sort(
(a, b) => Object.keys(a).length - Object.keys(b).length,
),
Array.from(result)
.sort((a, b) => Object.keys(a).length - Object.keys(b).length)
.map((item) => {
const { debugLabel: _, ...rest } = item
return rest
}),
).toStrictEqual([
{ toString: expect.any(Function), read: expect.any(Function) },
{
toString: expect.any(Function),
init: 0,
read: expect.any(Function),
write: expect.any(Function),
debugLabel: 'countAtom',
},
])
unsub()
Expand All @@ -88,7 +88,6 @@ describe('[DEV-ONLY] dev-only methods rev4', () => {
throw new Error('dev methods are not available')
}
const countAtom = atom(0)
countAtom.debugLabel = 'countAtom'
const derivedAtom = atom((get) => get(countAtom) * 2)
const unsub = store.sub(derivedAtom, vi.fn())
store.set(countAtom, 1)
Expand Down
16 changes: 15 additions & 1 deletion vitest.config.mts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { resolve } from 'path'
import { existsSync } from 'node:fs'
import { resolve } from 'node:path'
import react from '@vitejs/plugin-react'
// eslint-disable-next-line import/extensions
import { defineConfig } from 'vitest/config'

Expand All @@ -9,6 +11,18 @@ export default defineConfig({
{ find: /^jotai(.*)$/, replacement: resolve('./src/$1.ts') },
],
},
plugins: [
react({
babel: {
plugins: existsSync('./dist/babel/plugin-debug-label.js')
? [
// FIXME Can we read from ./src instead of ./dist?
'./dist/babel/plugin-debug-label.js',
]
: [],
},
}),
],
test: {
name: 'jotai',
// Keeping globals to true triggers React Testing Library's auto cleanup
Expand Down

0 comments on commit b059877

Please sign in to comment.