Skip to content

Commit

Permalink
add atomWithMutationState
Browse files Browse the repository at this point in the history
  • Loading branch information
kalijonn committed Dec 4, 2023
1 parent e26964e commit 076ccb3
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 30 deletions.
6 changes: 6 additions & 0 deletions __tests__/01_basic_spec.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {
atomWithInfiniteQuery,
atomWithMutation,
atomWithMutationState,
atomWithQuery,
atomWithSuspenseInfiniteQuery,
atomWithSuspenseQuery,
queryClientAtom,
} from '../src/index'

Expand All @@ -11,5 +14,8 @@ describe('basic spec', () => {
expect(atomWithQuery).toBeDefined()
expect(atomWithInfiniteQuery).toBeDefined()
expect(atomWithMutation).toBeDefined()
expect(atomWithSuspenseQuery).toBeDefined()
expect(atomWithSuspenseInfiniteQuery).toBeDefined()
expect(atomWithMutationState).toBeDefined()
})
})
19 changes: 4 additions & 15 deletions __tests__/atomWithInfiniteQuery_spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,6 @@ import { useAtom } from 'jotai/react'
import { atom } from 'jotai/vanilla'
import { atomWithInfiniteQuery } from '../src/index'

beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runAllTimers()
jest.useRealTimers()
})

it('infinite query basic test', async () => {
let resolve = () => {}
type DataResponse = { response: { count: number } }
Expand Down Expand Up @@ -188,8 +180,6 @@ it('infinite query with enabled', async () => {
})

it('infinite query with enabled 2', async () => {
jest.useRealTimers() // FIXME can avoid?

const enabledAtom = atom<boolean>(true)
const slugAtom = atom<string | null>('first')
type DataResponse = {
Expand All @@ -198,6 +188,7 @@ it('infinite query with enabled 2', async () => {
currentPage: number
}
}
let resolve = () => {}
const slugQueryAtom = atomWithInfiniteQuery<DataResponse>((get) => {
const slug = get(slugAtom)
const isEnabled = get(enabledAtom)
Expand All @@ -207,7 +198,7 @@ it('infinite query with enabled 2', async () => {
enabled: isEnabled,
queryKey: ['enabled_toggle'],
queryFn: async ({ pageParam }) => {
await new Promise<void>((r) => setTimeout(r, 100)) // FIXME can avoid?
await new Promise<void>((r) => (resolve = r))
return {
response: { slug: `hello-${slug}`, currentPage: pageParam as number },
}
Expand Down Expand Up @@ -264,17 +255,15 @@ it('infinite query with enabled 2', async () => {
)

await findByText('loading')
resolve()
await findByText('slug: hello-first')

await new Promise((r) => setTimeout(r, 100)) // FIXME we want to avoid this
fireEvent.click(getByText('set disabled'))
fireEvent.click(getByText('set slug'))

await new Promise((r) => setTimeout(r, 100)) // FIXME we want to avoid this
await findByText('slug: hello-first')

await new Promise((r) => setTimeout(r, 100)) // FIXME we want to avoid this
fireEvent.click(getByText('set enabled'))
resolve()
await findByText('slug: hello-world')
})

Expand Down
73 changes: 73 additions & 0 deletions __tests__/atomWithMutationState_spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react'
import { QueryClient } from '@tanstack/query-core'
import { fireEvent, render } from '@testing-library/react'
import { Provider, useAtom } from 'jotai'
import { atomWithMutation, atomWithMutationState } from '../src'

it('atomWithMutationState multiple', async () => {
const client = new QueryClient()
let resolve1: (() => void) | undefined
const mutateAtom1 = atomWithMutation<number, number>(
() => ({
mutationKey: ['test-atom'],
mutationFn: async (a) => {
await new Promise<void>((r) => {
resolve1 = r
})
return a
},
}),
() => client
)
let resolve2: (() => void) | undefined
const mutateAtom2 = atomWithMutation<number, number>(
() => ({
mutationKey: ['test-atom'],
mutationFn: async (a) => {
await new Promise<void>((r) => {
resolve2 = r
})
return a
},
}),
() => client
)

const mutationStateAtom = atomWithMutationState(
() => ({ filters: { mutationKey: ['test-atom'] } }),
() => client
)

function App() {
const [{ mutate: mutate1 }] = useAtom(mutateAtom1)
const [{ mutate: mutate2 }] = useAtom(mutateAtom2)
const [mutations] = useAtom(mutationStateAtom)

return (
<div>
<p>mutationCount: {mutations.length}</p>
<button
onClick={() => {
mutate1(1)
mutate2(2)
}}>
mutate
</button>
</div>
)
}

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

await findByText('mutationCount: 0')
fireEvent.click(getByText('mutate'))
await findByText('mutationCount: 2')
resolve1?.()
await findByText('mutationCount: 1')
resolve2?.()
await findByText('mutationCount: 0')
})
7 changes: 0 additions & 7 deletions __tests__/atomWithSuspenseInfiniteQuery_spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ import React, { StrictMode, Suspense } from 'react'
import { fireEvent, render } from '@testing-library/react'
import { useAtom } from 'jotai'
import { atomWithSuspenseInfiniteQuery } from '../src'
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runAllTimers()
jest.useRealTimers()
})

it('suspense basic, suspends', async () => {
let resolve = () => {}
Expand Down
7 changes: 0 additions & 7 deletions __tests__/atomWithSuspenseQuery_spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@ import { QueryClient } from '@tanstack/query-core'
import { fireEvent, render } from '@testing-library/react'
import { atom, useAtom, useSetAtom } from 'jotai'
import { atomWithSuspenseQuery } from '../src'
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.runAllTimers()
jest.useRealTimers()
})

it('suspense basic, suspends', async () => {
let resolve = () => {}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"postcompile": "cp dist/index.modern.mjs dist/index.modern.js && cp dist/index.modern.mjs.map dist/index.modern.js.map",
"test": "run-s eslint tsc-test jest",
"eslint": "eslint --ext .js,.ts,.tsx .",
"jest": "jest --watch",
"jest": "jest",
"tsc-test": "tsc --project . --noEmit",
"examples:01_typescript": "DIR=01_typescript EXT=tsx webpack serve",
"examples:02_refetch": "DIR=02_refetch EXT=tsx webpack serve",
Expand Down
55 changes: 55 additions & 0 deletions src/atomWithMutationState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
DefaultError,
Mutation,
MutationCache,
MutationFilters,
MutationState,
QueryClient,
} from '@tanstack/query-core'
import { Getter, atom } from 'jotai'
import { queryClientAtom } from './queryClientAtom'

type MutationStateOptions<TResult = MutationState> = {
filters?: MutationFilters
select?: (
mutation: Mutation<unknown, DefaultError, unknown, unknown>
) => TResult
}

function getResult<TResult = MutationState>(
mutationCache: MutationCache,
options: MutationStateOptions<TResult>
): Array<TResult> {
return mutationCache
.findAll({ ...options.filters, status: 'pending' })
.map(
(mutation): TResult =>
(options.select
? options.select(
mutation as Mutation<unknown, DefaultError, unknown, unknown>
)
: mutation.state) as TResult
)
}

export const atomWithMutationState = <TResult = MutationState>(
getOptions: (get: Getter) => MutationStateOptions<TResult>,
getQueryClient: (get: Getter) => QueryClient = (get) => get(queryClientAtom)
) => {
const resultsAtom = atom<TResult[]>([])
const observableAtom = atom((get) => {
const queryClient = getQueryClient(get)

const mutationCache = queryClient.getMutationCache()
resultsAtom.onMount = (set) => {
mutationCache.subscribe(() => {
set(getResult(getQueryClient(get).getMutationCache(), getOptions(get)))
})
}
})

return atom((get) => {
get(observableAtom)
return get(resultsAtom)
})
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export { atomWithSuspenseQuery } from './atomWithSuspenseQuery'
export { atomWithInfiniteQuery } from './atomWithInfiniteQuery'
export { atomWithMutation } from './atomWithMutation'
export { atomWithSuspenseInfiniteQuery } from './atomWithSuspenseInfiniteQuery'
export { atomWithMutationState } from './atomWithMutationState'

export * from './QueryAtomErrorResetBoundary'

0 comments on commit 076ccb3

Please sign in to comment.