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
メンテナーはふだん 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
- Mount a component with
useQuerywhose key depends on a signal, and acreateMemoreadingquery.data. - Change the signal (with
@solidjs/diagnosticscapturing, attribution on). - Observe
EFFECT_RELAY_TEARfor 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
- OS: macOS 13
- Browser: Brave (Chromium), also reproduces under vitest + jsdom
[email protected],@solidjs/[email protected]@tanstack/[email protected](sameuseBaseQueryin rc.5)
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
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
TanStack/query のほかの issue
-
solid-query: STRICT_READ_UNTRACKED on Solid 2 — client()/options() read in component body (useMutation, useBaseQuery)再び着手できるかも このイシューのプルリクエストはマージされずにクローズされました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
TanStack/query#11358 · コメント 2 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
-
solid-query: switching the queryClient accessor strands the new client's cache (subscription stays on the old observer)対応中かも @MaNaN1803 が 74 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
TanStack/query#11106 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
setQueryData: NoInfer loses discriminated-union members when spreading a narrowed updater value対応中かも @iosayin が今日担当しました。 オープン
難易度 4/5 3〜5日 初心者へのやさしさ 55/100
メンテナーはふだん 1 日以内に返信
-
setQueryData updater loses discriminated-union fields when spreading inferred NoInfer data対応中かも @boriskozak が今日担当しました。 オープン
難易度 4/5 3〜5日 初心者へのやさしさ 68/100
TanStack/query#11794 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[vue-query]: UseMutationReturnType default names unexported MutationResult (TS2883) 🤖🤖🤖対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 3/5 1〜2日 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 67/100
ehmpathy/rhachet-roles-bhrain#586 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
OHDSI/Data2Evidence#3496 ·
メンテナーはふだん 2 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
apache/rocketmq-dashboard#5594 ·
メンテナーはふだん 3 日以内に返信
-
react-doctor severity:warning tech-debt
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
digidem/comapeo-cloud-app#418 ·
メンテナーはふだん 1 日以内に返信