`onFocusChange` fires again after each render, also when the focused point does not change
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 68/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- react, typescript
- Área
- data-visualization, frontend
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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:
- The pointer focuses a point.
onFocusChange(point)callssetFocused(point). - React renders again. Something in the definition depends on the focus (for example a highlighted series), so the definition is a new object.
- The adapter calls
update. The renderer restores the same point as a new object and callsonFocusChange(newObject). 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
- https://github.com/TanStack/charts/pull/111 (docs: React definition ownership and memoization)
Written by an agent (Claude Code, claude-opus-5-5).
- Lenguaje dominante
- TypeScript
- Estrellas
- 769
- Forks
- 47
- Merge medio
- 6 h 9 min
- PR fusionados (30 d)
- 20
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de TanStack/charts
-
Renderer treats a sub-pixel width difference as a resize, which cancels the `motion()` entranceAbierto
Dificultad 1/5 Menos de una hora Aptitud para principiantes 85/100
Los mantenedores suelen responder en 1 día
-
brushX with `values` can shorten the selection range when it’s dragged to the plot edgePosiblemente ocupada @andersk la tomó hace 2 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 54/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 52/100
Los mantenedores suelen responder en 1 día
Todos los issues de TanStack/charts
Issues similares
-
First unknown-user login after boot is one scrypt run slower than a real user's wrong passwordAbiertoarea: backend bug priority: low
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
snapotter-hq/SnapOtter#2254 ·
Los mantenedores suelen responder en 1 día
-
bug ticket
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
cratestack/cratestack#1154 ·
Los mantenedores suelen responder en 1 día
-
server 消息处理器 cmd 分支补显式错误回报——竞态非法命令现走未处理拒绝Posiblemente ocupada @openaddr la tomó hoy. Abiertoready-for-agent refactor wayfinder:task
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
openaddr/dafung-web#428 ·
Los mantenedores suelen responder en 1 día
-
Flaky: mongodb-memory-server 'Port already in use' when another process starts a mongod concurrentlyAbiertoarea:testing bug effort:S priority:P2
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
Los mantenedores suelen responder en 1 día
-
lens:agent lens:process process
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
thebristolsound/birdbrain#1772 ·
Los mantenedores suelen responder en 1 día