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

`onFocusChange` fires again after each render, also when the focused point does not change

クローズ
#178 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

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

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

評価

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

調査の方向性

Start with the focus restoration and callback logic in dist/renderer.js around lines 157–172, then follow how a definition update reaches the renderer. Check whether tests cover repeated renders with an unchanged focused-point key and focus groups. Done means callbacks fire when focus identity changes, but not when a render restores the same focus.

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

説明

Version: @tanstack/charts 1.0.0, React adapter (@tanstack/charts/react)

Problem

After each render that had a focused point, the renderer calls onFocusChange and onFocusGroupChange again. It does this also when the focused point has the same key as before. The point is a new object each time, because restoreChartFocusPoint returns the point from the new scene.

In dist/renderer.js (about lines 157 to 172):

const nextFocusedPoints = trackedPointer
  ? resolvePointerFocus(...)
  : previousFocusedPoint
    ? (() => {
        const restored = restoreChartFocusPoint(presentedPoints, previousFocusedPoint);
        return restored ? focusPointsForPoint(restored, presentedPoints) : [];
      })()
    : [];
const nextFocusedPoint = nextFocusedPoints[0] ?? null;
...
if (previousFocusedPoint || nextFocusedPoint || previousCursorPresentation) {
  ...
  options.onFocusChange?.(nextFocusedPoint);
  options.onFocusGroupChange?.(nextFocusedPoints);
}

A render occurs when the definition identity changes (definitionChanged in renderer.js, about line 686). In React, a definition that is not memoized, or a memo dependency that changes with the focus, causes a new render.

The usual React pattern is to put the focused point in state. With that pattern, the result is a render loop:

  1. The pointer focuses a point. onFocusChange(point) calls setFocused(point).
  2. React renders again. Something in the definition depends on the focus (for example a highlighted series), so the definition is a new object.
  3. The adapter calls update. The renderer restores the same point as a new object and calls onFocusChange(newObject).
  4. setFocused(newObject) does not bail out, because the object is new. Go to step 2.

Minimal repro

import { useMemo, useState } from 'react';
import { defineChart, lineY, type ChartPoint } from '@tanstack/charts';
import { Chart } from '@tanstack/charts/react';
import { scaleLinear } from '@tanstack/charts/scales/linear';
import { tooltip } from '@tanstack/charts/tooltip';

const rows = [
  { x: 0, y: 1 },
  { x: 1, y: 3 },
  { x: 2, y: 2 },
];

let renders = 0;

export function Repro() {
  const [focused, setFocused] = useState<ChartPoint | null>(null);

  // The definition depends on the focus, which is a normal thing to do.
  const definition = useMemo(
    () =>
      defineChart({
        marks: [
          lineY(rows, {
            x: 'x',
            y: 'y',
            strokeWidth: focused ? 3 : 1,
          }),
        ],
        scales: { x: { scale: scaleLinear }, y: { scale: scaleLinear } },
        tooltip,
      }),
    [focused],
  );

  console.log('render', ++renders);

  return (
    <Chart
      definition={definition}
      ariaLabel="Repro"
      width={400}
      height={200}
      onFocusChange={setFocused}
    />
  );
}

Move the pointer over a point and keep it there.

Expected

onFocusChange fires once when the focused point changes. It does not fire again when a render restores the same point.

Actual

onFocusChange fires after every render with a new point object for the same key. The component renders again and again while the pointer stays on the point.

Our workaround

We store a primitive, so React bails out of the state update:

const handleFocusChange = useCallback((point: ChartPoint | null) => {
  setActiveIndex(point ? point.datumIndex : null);
}, []);

This works, but each consumer must know about it. Nothing in the docs tells you about the risk.

Suggested fix

Call onFocusChange and onFocusGroupChange only when the focus identity changes, for example with sameChartPointIdentity(previousFocusedPoint, nextFocusedPoint) and the same check on each point of the group. If a re-emit on each render is intentional, please document it in the React reference for onFocusChange, next to the memoization guidance from https://github.com/TanStack/charts/pull/111.

Related


Written by an agent (Claude Code, claude-opus-5-5).

主要言語
TypeScript
スター
842
フォーク
53
平均マージ
4時間 40分
マージ済み PR(30日)
16

環境構築

はじめの一歩

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

TanStack/charts のほかの issue

TanStack/charts の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

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

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