Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

solid-query: EFFECT_RELAY_TEAR on Solid 2 when a query key changes — useBaseQuery relays the result through a version signal written from a render effect

オープン
#11,903 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
45/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
typescript
領域
frontend

調査の方向性

The adapter's useBaseQuery (the payload points at build/index.js) wires createRenderEffect(() => defaultedOptions(), opts => observer.setOptions(opts)), then relays cache events into a version signal read by query. Start by reproducing the key switch with the issue's captureArtifact snippet under vitest/jsdom and confirming EFFECT_RELAY_TEAR. Trace whether the observer result can be derived as a memo or projection of the current options instead of a counter written from onCacheEvent; done means the diagnostic no longer fires and query.data shows the new key's value in the same flush.

索引モデルが issue の本文から書いたものです。

説明

Describe the bug

With Solid 2 ([email protected]) and @tanstack/[email protected] (the useBaseQuery code is identical in 6.0.0-rc.5), switching the query key of a useQuery logs a reactivity diagnostic from @solidjs/[email protected]:

[EFFECT_RELAY_TEAR] memo "computed" ran twice for one write of "tariff": once in the flush where "tariff" changed,
and again after effect "effect" relayed it by writing "signal" — the first frame showed the new "tariff" with the stale "signal".
If "signal" is computed from what the effect reads, make it a memo so readers get it in the same flush;
if the write reads something outside the graph (layout, time), the tear is the cost of measuring.

Owner path: <QueryClientProvider> › <provider> › computed › <Price> › computed — the only user code is a createMemo reading query.data. One diagnostic per useQuery instance whose key changed (a screen with five queries logs five).

Solid 2 dev builds treat these diagnostics as actionable (the console is expected to stay clean), so every app that derives anything from query.data and ever changes a key gets a warning it cannot fix on its side.

Where it comes from

useBaseQuery (build/index.js):

createRenderEffect(
  () => defaultedOptions(),
  (opts) => observer.setOptions(opts)
);
const [version, setVersion] = createSignal(0, { ownedWrite: true });
const onCacheEvent = (event) => {
  if (event.query.queryHash === untrack(defaultedOptions).queryHash || event.query.queryHash === latestHash) {
    setVersion((v) => v + 1);
  }
};
...
const query = () => {
  version();
  return lookupQuery();
};

When the key changes, defaultedOptions is recomputed in the same flush as the key signal; readers of query.data see the new options with the old observer result, then the render effect calls observer.setOptions, the cache event bumps version, and every memo downstream recomputes a second time. That is exactly the "effect relays a derived value by writing a signal" pattern the diagnostic describes: version is derived from what the effect reads, so the first frame is torn.

Your minimal, reproducible example
import { captureArtifact } from '@solidjs/diagnostics';
import { render } from '@solidjs/testing-library';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/solid-query';
import { createMemo, createSignal, flush } from 'solid-js';

function Price(props: { tariff: string }) {
  const query = useQuery(() => ({ queryKey: ['price', props.tariff], queryFn: async () => props.tariff.length }));
  const doubled = createMemo(() => (query.data ?? 0) * 2);
  return <span>{doubled()}</span>;
}

const client = new QueryClient({ defaultOptions: { queries: { staleTime: Infinity, retry: false } } });
client.setQueryData(['price', 'base'], 1);
client.setQueryData(['price', 'site'], 2);
const [tariff, setTariff] = createSignal('base');

const { artifact } = await captureArtifact(async () => {
  const screen = render(() => (
    <QueryClientProvider client={client}>
      <Price tariff={tariff()} />
    </QueryClientProvider>
  ));
  flush();
  setTariff('site');          // key change
  flush();
  await new Promise((r) => setTimeout(r, 30));
  flush();
  screen.unmount();
}, { scenario: 'useQuery key switch', attribution: true });

console.log(artifact.diagnostics.map((e) => e.code)); // ['EFFECT_RELAY_TEAR']

Both keys are already in the cache, so no fetch is involved — the second recompute is purely the version relay.

Steps to reproduce
  1. Mount a component with useQuery whose key depends on a signal, and a createMemo reading query.data.
  2. Change the signal (with @solidjs/diagnostics capturing, attribution on).
  3. Observe EFFECT_RELAY_TEAR for the memo.
Expected behavior

Changing a query key should produce one consistent frame: the result for the new key arrives in the same flush as the key (e.g. the observer result derived as a memo / projection of the options instead of being relayed through a counter signal written from a cache subscription), or the diagnostic should not fire for useQuery consumers.

Platform
TanStack Query adapter

solid-query

TanStack Query version

6.0.0-rc.4 / 6.0.0-rc.5

TypeScript version

7.0

Additional context

Related: #11358 (STRICT_READ_UNTRACKED in the same adapter under Solid 2 diagnostics).

主要言語
TypeScript
スター
50.4k
フォーク
4.2k
平均マージ
11時間 33分
マージ済み PR(30日)
421

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

TanStack/query のほかの issue

TanStack/query の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。