Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/eight-wombats-bet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@suspensive/react-query-4": minor
"@suspensive/react-query-5": minor
"@suspensive/react-query": minor
---

feat(react-query): add IsMutating component
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
description: 'Show global mutating indicators with IsMutating component. Declarative alternative to useIsMutating hook.'
---

import { Callout } from '@/components'

# IsMutating

<Callout type='experimental'>

`<IsMutating/>` is an experimental feature, so this interface may change.

</Callout>

Render-prop component that exposes the number of mutations currently running (from TanStack Query's `useIsMutating`). Useful for showing saving or submitting states without adding hooks in every component.

```tsx /IsMutating basic/
import { IsMutating } from '@suspensive/react-query'

const GlobalMutatingIndicator = () => (
<IsMutating>{(isMutating) => (isMutating ? <Spinner /> : null)}</IsMutating>
)
```

### Filters (MutationFilters)

You can scope the indicator with the same filters accepted by TanStack Query's `useIsMutating` (e.g., `mutationKey`, `predicate`).

```tsx /IsMutating with filters/
import { IsMutating } from '@suspensive/react-query'

const PostsMutatingIndicator = () => (
<IsMutating mutationKey={['posts']}>
{(isMutating) => (isMutating ? <Spinner /> : null)}
</IsMutating>
)
```

### Props

- **children**: `(isMutating: number) => ReactNode`
- **...MutationFilters**: Same shape as TanStack Query's `MutationFilters` for `useIsMutating`.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default {
QueriesHydration: { title: '<QueriesHydration/>' },
QueryClientConsumer: { title: '<QueryClientConsumer/>' },
IsFetching: { title: '<IsFetching/>' },
IsMutating: { title: '<IsMutating/>' },
createGetQueryClient: { title: 'createGetQueryClient' },
usePrefetchQuery: { title: 'usePrefetchQuery' },
usePrefetchInfiniteQuery: { title: 'usePrefetchInfiniteQuery' },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
description: 'IsMutating 컴포넌트로 전역 뮤테이션 인디케이터 표시. useIsMutating 훅의 선언적 대안.'
---

import { Callout } from '@/components'

# IsMutating

<Callout type='experimental'>

`<IsMutating/>`는 실험 기능이므로 이 인터페이스는 변경될 수 있습니다.

</Callout>

현재 실행 중인 뮤테이션 개수(TanStack Query의 `useIsMutating`)를 render-prop으로 전달하는 컴포넌트입니다. 저장 중 상태나 제출 중 상태를 훅을 추가하지 않고도 표시할 수 있습니다.

```tsx /IsMutating basic/
import { IsMutating } from '@suspensive/react-query'

const GlobalMutatingIndicator = () => (
<IsMutating>{(isMutating) => (isMutating ? <Spinner /> : null)}</IsMutating>
)
```

### 필터 (MutationFilters)

TanStack Query의 `useIsMutating`이 받는 `MutationFilters`와 동일한 필터(`mutationKey`, `predicate` 등)로 범위를 좁힐 수 있습니다.

```tsx /IsMutating with filters/
import { IsMutating } from '@suspensive/react-query'

const PostsMutatingIndicator = () => (
<IsMutating mutationKey={['posts']}>
{(isMutating) => (isMutating ? <Spinner /> : null)}
</IsMutating>
)
```

### Props

- **children**: `(isMutating: number) => ReactNode`
- **...MutationFilters**: TanStack Query `useIsMutating`의 `MutationFilters`와 동일한 형태
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default {
QueriesHydration: { title: '<QueriesHydration/>' },
QueryClientConsumer: { title: '<QueryClientConsumer/>' },
IsFetching: { title: '<IsFetching/>' },
IsMutating: { title: '<IsMutating/>' },
createGetQueryClient: { title: 'createGetQueryClient' },
usePrefetchQuery: { title: 'usePrefetchQuery' },
usePrefetchInfiniteQuery: { title: 'usePrefetchInfiniteQuery' },
Expand Down
36 changes: 36 additions & 0 deletions packages/react-query-4/src/IsMutating.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { render, screen } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { IsMutating } from './IsMutating'

const { useIsMutatingMock } = vi.hoisted(() => ({
useIsMutatingMock: vi.fn(() => 0),
}))

vi.mock('@tanstack/react-query', async () => {
const actual = await vi.importActual('@tanstack/react-query')
return {
...actual,
useIsMutating: useIsMutatingMock,
}
})

describe('<IsMutating/>', () => {
afterEach(() => {
useIsMutatingMock.mockReset()
useIsMutatingMock.mockReturnValue(0)
})

it('should forward mutation filters to useIsMutating', () => {
useIsMutatingMock.mockReturnValue(3)

render(
<IsMutating mutationKey={['todos']}>
{(isMutating) => <div data-testid="is-mutating">{isMutating}</div>}
</IsMutating>
)

expect(useIsMutatingMock).toHaveBeenCalledTimes(1)
expect(useIsMutatingMock).toHaveBeenCalledWith({ mutationKey: ['todos'] })
expect(screen.getByTestId('is-mutating')).toHaveTextContent('3')
})
})
16 changes: 16 additions & 0 deletions packages/react-query-4/src/IsMutating.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ReactNode } from 'react'
import { describe, expectTypeOf, it } from 'vitest'
import { IsMutating } from './IsMutating'
import { queryKey } from './test-utils'

describe('<IsMutating/>', () => {
it('type check', () => {
void (() => <IsMutating>{(isMutating) => expectTypeOf(isMutating).toEqualTypeOf<number>()}</IsMutating>)()
void (() => (
<IsMutating mutationKey={queryKey}>{(isMutating) => expectTypeOf(isMutating).toEqualTypeOf<number>()}</IsMutating>
))()

expectTypeOf(<IsMutating>{() => <></>}</IsMutating>).toEqualTypeOf<React.JSX.Element>()
expectTypeOf(<IsMutating>{() => <></>}</IsMutating>).not.toEqualTypeOf<ReactNode>()
})
})
14 changes: 14 additions & 0 deletions packages/react-query-4/src/IsMutating.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use client'
import { type MutationFilters, useIsMutating } from '@tanstack/react-query'
import type { ReactNode } from 'react'

/**
* Renders the current mutating count for mutations that match the given filters.
* @experimental This is an experimental feature.
*/
export const IsMutating = ({
children,
...filter
}: MutationFilters & { children: (isMutating: ReturnType<typeof useIsMutating>) => ReactNode }) => (
<>{children(useIsMutating(filter))}</>
)
1 change: 1 addition & 0 deletions packages/react-query-4/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export { useSuspenseQuery } from './useSuspenseQuery'
export type { UseSuspenseQueryOptions, UseSuspenseQueryResult } from './useSuspenseQuery'

export { IsFetching } from './IsFetching'
export { IsMutating } from './IsMutating'
export { Mutation } from './Mutation'
export { mutationOptions } from './mutationOptions'
export { PrefetchInfiniteQuery } from './PrefetchInfiniteQuery'
Expand Down
16 changes: 16 additions & 0 deletions packages/react-query-5/src/IsMutating.test-d.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ReactNode } from 'react'
import { describe, expectTypeOf, it } from 'vitest'
import { IsMutating } from './IsMutating'
import { queryKey } from './test-utils'

describe('<IsMutating/>', () => {
it('type check', () => {
void (() => <IsMutating>{(isMutating) => expectTypeOf(isMutating).toEqualTypeOf<number>()}</IsMutating>)()
void (() => (
<IsMutating mutationKey={queryKey}>{(isMutating) => expectTypeOf(isMutating).toEqualTypeOf<number>()}</IsMutating>
))()

expectTypeOf(<IsMutating>{() => <></>}</IsMutating>).toEqualTypeOf<React.JSX.Element>()
expectTypeOf(<IsMutating>{() => <></>}</IsMutating>).not.toEqualTypeOf<ReactNode>()
})
})
14 changes: 14 additions & 0 deletions packages/react-query-5/src/IsMutating.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use client'
import { type MutationFilters, useIsMutating } from '@tanstack/react-query'
import type { ReactNode } from 'react'

/**
* Renders the current mutating count for mutations that match the given filters.
* @experimental This is an experimental feature.
*/
export const IsMutating = ({
children,
...filter
}: MutationFilters & {
children: (isMutating: ReturnType<typeof useIsMutating>) => ReactNode
}) => <>{children(useIsMutating(filter))}</>
1 change: 1 addition & 0 deletions packages/react-query-5/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export { useSuspenseQuery } from './useSuspenseQuery'
export type { UseSuspenseQueryOptions, UseSuspenseQueryResult } from './useSuspenseQuery'

export { IsFetching } from './IsFetching'
export { IsMutating } from './IsMutating'
export { Mutation } from './Mutation'
export { mutationOptions } from './mutationOptions'
export { PrefetchInfiniteQuery } from './PrefetchInfiniteQuery'
Expand Down
2 changes: 2 additions & 0 deletions packages/react-query/src/bin/utils/package.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ describe('package', () => {
'PrefetchQuery',
'PrefetchInfiniteQuery',
'Mutation',
'IsMutating',
'IsFetching',
]

Expand All @@ -96,6 +97,7 @@ describe('package', () => {
'PrefetchQuery',
'PrefetchInfiniteQuery',
'Mutation',
'IsMutating',
'IsFetching',
]

Expand Down
1 change: 1 addition & 0 deletions packages/react-query/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export { usePrefetchQuery } from '@suspensive/react-query-4'
export { usePrefetchInfiniteQuery } from '@suspensive/react-query-4'

export { IsFetching } from '@suspensive/react-query-4'
export { IsMutating } from '@suspensive/react-query-4'
export { SuspenseQuery } from '@suspensive/react-query-4'
export { SuspenseQueries } from '@suspensive/react-query-4'
export { SuspenseInfiniteQuery } from '@suspensive/react-query-4'
Expand Down
1 change: 1 addition & 0 deletions packages/react-query/src/v4.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export { usePrefetchQuery } from '@suspensive/react-query-4'
export { usePrefetchInfiniteQuery } from '@suspensive/react-query-4'

export { IsFetching } from '@suspensive/react-query-4'
export { IsMutating } from '@suspensive/react-query-4'
export { SuspenseQuery } from '@suspensive/react-query-4'
export { SuspenseQueries } from '@suspensive/react-query-4'
export { SuspenseInfiniteQuery } from '@suspensive/react-query-4'
Expand Down
1 change: 1 addition & 0 deletions packages/react-query/src/v5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { usePrefetchQuery } from '@suspensive/react-query-5'
export { usePrefetchInfiniteQuery } from '@suspensive/react-query-5'

export { IsFetching } from '@suspensive/react-query-5'
export { IsMutating } from '@suspensive/react-query-5'
export { SuspenseQuery } from '@suspensive/react-query-5'
export { SuspenseQueries } from '@suspensive/react-query-5'
export { SuspenseInfiniteQuery } from '@suspensive/react-query-5'
Expand Down
Loading