From 771e53bd03195e731a4c9b8a0966a3f57d0a13e4 Mon Sep 17 00:00:00 2001 From: Brenley Dueck Date: Mon, 23 Oct 2023 19:04:17 -0500 Subject: [PATCH] feat(solid-query): Add useMutationState --- .../src/__tests__/useMutationState.test.tsx | 90 +++++++++++++++++++ packages/solid-query/src/__tests__/utils.tsx | 2 + packages/solid-query/src/useMutationState.ts | 63 +++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 packages/solid-query/src/__tests__/useMutationState.test.tsx create mode 100644 packages/solid-query/src/useMutationState.ts diff --git a/packages/solid-query/src/__tests__/useMutationState.test.tsx b/packages/solid-query/src/__tests__/useMutationState.test.tsx new file mode 100644 index 0000000000..aba5da172d --- /dev/null +++ b/packages/solid-query/src/__tests__/useMutationState.test.tsx @@ -0,0 +1,90 @@ +import { describe, expect, expectTypeOf, it } from 'vitest' +import { fireEvent, render, waitFor } from '@solidjs/testing-library' +import { createEffect } from 'solid-js' +import { useMutationState } from '../useMutationState' +import { createMutation } from '../createMutation' +import { QueryClientProvider } from '../QueryClientProvider' +import { createQueryClient, doNotExecute, sleep } from './utils' +import type { MutationState, MutationStatus } from '@tanstack/query-core' + +describe('useMutationState', () => { + describe('types', () => { + it('should default to QueryState', () => { + doNotExecute(() => { + const result = useMutationState(() => ({ + filters: { status: 'pending' }, + })) + + expectTypeOf>(result()) + }) + }) + it('should infer with select', () => { + doNotExecute(() => { + const result = useMutationState(() => ({ + filters: { status: 'pending' }, + select: (mutation) => mutation.state.status, + })) + + expectTypeOf>(result()) + }) + }) + }) + it('should return variables after calling mutate', async () => { + const queryClient = createQueryClient() + const variables: Array> = [] + const mutationKey = ['mutation'] + + function Variables() { + const states = useMutationState(() => ({ + filters: { mutationKey, status: 'pending' }, + select: (mutation) => mutation.state.variables, + })) + + createEffect(() => { + variables.push(states()) + }) + + return null + } + + function Mutate() { + const mutation = createMutation(() => ({ + mutationKey, + mutationFn: async (input: number) => { + await sleep(150) + return 'data' + input + }, + })) + + return ( +
+ data: {mutation.data ?? 'null'} + +
+ ) + } + + function Page() { + return ( +
+ + +
+ ) + } + + const rendered = render(() => ( + + + + )) + + await waitFor(() => rendered.getByText('data: null')) + + fireEvent.click(rendered.getByRole('button', { name: /mutate/i })) + + await waitFor(() => rendered.getByText('data: data1')) + + expect(variables).toEqual([[], [1], []]) + }) +}) diff --git a/packages/solid-query/src/__tests__/utils.tsx b/packages/solid-query/src/__tests__/utils.tsx index b143a0d8c9..70e2abaa24 100644 --- a/packages/solid-query/src/__tests__/utils.tsx +++ b/packages/solid-query/src/__tests__/utils.tsx @@ -66,3 +66,5 @@ export function setActTimeout(fn: () => void, ms?: number) { export function expectTypeNotAny(_: 0 extends 1 & T ? never : T): void { return undefined } + +export const doNotExecute = (_func: () => void) => true diff --git a/packages/solid-query/src/useMutationState.ts b/packages/solid-query/src/useMutationState.ts new file mode 100644 index 0000000000..e04a8c115b --- /dev/null +++ b/packages/solid-query/src/useMutationState.ts @@ -0,0 +1,63 @@ +import { createEffect, createMemo, createSignal, onCleanup } from 'solid-js' +import { replaceEqualDeep } from '@tanstack/query-core' +import { useQueryClient } from './QueryClientProvider' +import type { + DefaultError, + Mutation, + MutationCache, + MutationFilters, + MutationState, +} from '@tanstack/query-core' +import type { Accessor } from 'solid-js' +import type { QueryClient } from './QueryClient' + +type MutationStateOptions = { + filters?: MutationFilters + select?: ( + mutation: Mutation, + ) => TResult +} + +function getResult( + mutationCache: MutationCache, + options: MutationStateOptions, +): Array { + return mutationCache + .findAll(options.filters) + .map( + (mutation): TResult => + (options.select + ? options.select( + mutation as Mutation, + ) + : mutation.state) as TResult, + ) +} + +export function useMutationState( + options: Accessor> = () => ({}), + queryClient?: Accessor, +): Accessor> { + const client = createMemo(() => useQueryClient(queryClient?.())) + const mutationCache = createMemo(() => client().getMutationCache()) + + const [result, setResult] = createSignal( + getResult(mutationCache(), options()), + ) + + createEffect(() => { + const unsubscribe = mutationCache().subscribe(() => { + const nextResult = replaceEqualDeep( + result(), + getResult(mutationCache(), options()), + ) + if (result() !== nextResult) { + setResult(nextResult) + } + }) + + onCleanup(unsubscribe) + }) + + return result +}