diff --git a/src/utils.tsx b/src/utils.tsx index 43d0e19..c48aaf6 100644 --- a/src/utils.tsx +++ b/src/utils.tsx @@ -1,2 +1,3 @@ +export { useAtomCallback } from './utils/useAtomCallback' export { useResetAtom } from './utils/useResetAtom' export { useHydrateAtoms } from './utils/useHydrateAtoms' diff --git a/src/utils/useAtomCallback.ts b/src/utils/useAtomCallback.ts new file mode 100644 index 0000000..cc6e68d --- /dev/null +++ b/src/utils/useAtomCallback.ts @@ -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[1] + +export function useAtomCallback( + 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) +} diff --git a/test/useAtomCallback.test.tsx b/test/useAtomCallback.test.tsx new file mode 100644 index 0000000..bd4c121 --- /dev/null +++ b/test/useAtomCallback.test.tsx @@ -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 ( + <> +
atom count: {count()}
+ + + ) + } + + 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 ( + <> +
state count: {count()}
+ + ) + } + + const Parent = () => { + return ( + <> + + + + ) + } + + const { findByText, getByText } = render(() => ) + + 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 ( + <> +
count: {count()}
+ + + ) + } + + 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 ( + <> +
changeable count: {changeableCount()}
+ + ) + } + + const Parent = () => { + return ( + <> + + + + ) + } + + const { findByText, getByText } = render(() => ) + + 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 ( +
+

count: {count()}

+ +
+ ) + } + + const { findByText, getByText } = render(() => ) + + 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 ( + <> +
atom count: {count()}
+ + + ) + } + + const { findByText, getByText } = render(() => ) + + await findByText('atom count: 0') + + fireEvent.click(getByText('dispatch')) + await findByText('atom count: 1') +})