diff --git a/.changeset/solid-query-mutation-state-untrack.md b/.changeset/solid-query-mutation-state-untrack.md new file mode 100644 index 0000000000..225b48d77c --- /dev/null +++ b/.changeset/solid-query-mutation-state-untrack.md @@ -0,0 +1,7 @@ +--- +'@tanstack/solid-query': patch +--- + +fix(solid-query): stop `useMutationState` making the computation that triggers a mutation depend on its result + +The mutation-cache subscription read the hook's own result signal. `MutationCache` notifies synchronously, so that read happened while the computation that called `mutate` was still the active listener — registering the signal as one of its dependencies, which `setResult` then immediately invalidated. Calling `mutate` from inside an effect therefore re-ran that effect on every mutation, and an unguarded `mutate` looped. The read is now untracked. diff --git a/packages/solid-query/src/__tests__/useMutationState.test.tsx b/packages/solid-query/src/__tests__/useMutationState.test.tsx index 8bc837fb8f..1e1860a8a5 100644 --- a/packages/solid-query/src/__tests__/useMutationState.test.tsx +++ b/packages/solid-query/src/__tests__/useMutationState.test.tsx @@ -111,4 +111,36 @@ describe('useMutationState', () => { expect(variables).toEqual([[], [1], []]) }) + + it('should not make the computation that triggers a mutation depend on its result', async () => { + const mutationKey = queryKey() + let effectRuns = 0 + + function Page() { + const states = useMutationState(() => ({ filters: { mutationKey } })) + const mutation = useMutation(() => ({ + mutationKey, + mutationFn: (input: number) => sleep(150).then(() => 'data' + input), + })) + + createEffect(() => { + effectRuns++ + // Guarded so that a genuine feedback loop still terminates the test. + if (effectRuns === 1) { + mutation.mutate(1) + } + }) + + return