Skip to content

Commit

Permalink
Merge pull request #6 from rtritto/add-useAtomCallback
Browse files Browse the repository at this point in the history
feat: add useAtomCallback util
  • Loading branch information
wobsoriano authored Sep 20, 2024
2 parents 13fb31a + ac0c1a3 commit 4bc3ced
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { useAtomCallback } from './utils/useAtomCallback'
export { useResetAtom } from './utils/useResetAtom'
export { useHydrateAtoms } from './utils/useHydrateAtoms'
13 changes: 13 additions & 0 deletions src/utils/useAtomCallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// https://github.com/pmndrs/jotai/blob/main/src/react/utils/useAtomCallback.ts
import type { Getter, Setter } from 'jotai/vanilla'
import { atom, useSetAtom } from '../'

type Options = Parameters<typeof useSetAtom>[1]

export function useAtomCallback<Result, Args extends unknown[]>(
callback: (get: Getter, set: Setter, ...arg: Args) => Result,
options?: Options,
): (...args: Args) => Result {
const anAtom = atom(null, (get, set, ...args: Args) => callback(get, set, ...args))
return useSetAtom(anAtom, options)
}
174 changes: 174 additions & 0 deletions test/useAtomCallback.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// https://github.com/pmndrs/jotai/blob/main/tests/react/utils/useAtomCallback.test.tsx
import { it } from 'vitest'
import { createEffect, createSignal } from 'solid-js'
import { fireEvent, render, waitFor } from '@solidjs/testing-library'
import { atom, useAtom } from '../src'
import { useAtomCallback } from '../src/utils'

it('useAtomCallback with get', async () => {
const countAtom = atom(0)

const Counter = () => {
const [count, setCount] = useAtom(countAtom)
return (
<>
<div>atom count: {count()}</div>
<button onClick={() => setCount(c => c + 1)}>dispatch</button>
</>
)
}

const Monitor = () => {
const [count, setCount] = createSignal(0)
const readCount = useAtomCallback(
(get) => {
const currentCount = get(countAtom)
setCount(currentCount)
return currentCount
},
)
createEffect(() => {
const timer = setInterval(() => {
readCount()
}, 10)
return () => {
clearInterval(timer)
}
})
return (
<>
<div>state count: {count()}</div>
</>
)
}

const Parent = () => {
return (
<>
<Counter />
<Monitor />
</>
)
}

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

await findByText('atom count: 0')
fireEvent.click(getByText('dispatch'))
await waitFor(() => {
getByText('atom count: 1')
getByText('state count: 1')
})
})

it('useAtomCallback with set and update', async () => {
const countAtom = atom(0)
const changeableAtom = atom(0)
const Counter = () => {
const [count, setCount] = useAtom(countAtom)
return (
<>
<div>count: {count()}</div>
<button onClick={() => setCount(c => c + 1)}>dispatch</button>
</>
)
}

const Monitor = () => {
const [changeableCount] = useAtom(changeableAtom)
const changeCount = useAtomCallback(
(get, set) => {
const currentCount = get(countAtom)
set(changeableAtom, currentCount)
return currentCount
},
)
createEffect(() => {
const timer = setInterval(() => {
changeCount()
}, 10)
return () => {
clearInterval(timer)
}
})
return (
<>
<div>changeable count: {changeableCount()}</div>
</>
)
}

const Parent = () => {
return (
<>
<Counter />
<Monitor />
</>
)
}

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

await findByText('count: 0')
fireEvent.click(getByText('dispatch'))
await waitFor(() => {
getByText('count: 1')
getByText('changeable count: 1')
})
})

it('useAtomCallback with set and update and arg', async () => {
const countAtom = atom(0)

const App = () => {
const [count] = useAtom(countAtom)
const setCount = useAtomCallback(
(_get, set, arg: number) => {
set(countAtom, arg)
return arg
},
)

return (
<div>
<p>count: {count()}</p>
<button onClick={() => setCount(42)}>dispatch</button>
</div>
)
}

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

await findByText('count: 0')
fireEvent.click(getByText('dispatch'))
await waitFor(() => {
getByText('count: 42')
})
})

it('useAtomCallback with sync atom (#1100)', async () => {
const countAtom = atom(0)

const Counter = () => {
const [count, setCount] = useAtom(countAtom)
const readCount = useAtomCallback(get => get(countAtom))
createEffect(() => {
const promiseOrValue = readCount()
if (typeof promiseOrValue !== 'number')
throw new Error('should return number')
})
return (
<>
<div>atom count: {count()}</div>
<button onClick={() => setCount(c => c + 1)}>dispatch</button>
</>
)
}

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

await findByText('atom count: 0')

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

0 comments on commit 4bc3ced

Please sign in to comment.