mirror of
https://github.com/apache/superset.git
synced 2026-05-21 15:55:10 +00:00
fix(AnnotationLayer): stop double-fetching chart on hydration + drop dead useCallback
Two fixes from @sadpandajoe's review. 1. **Double-fetch.** `fetchAppliedChart` synchronously sets both `value` and `slice` from one API response. The value-change watcher then saw `value` changed and called `fetchSliceData(value.value)` — re-resolving the same chart and overwriting the slice we just set. Fix: gate the watcher's `fetchSliceData` on `!slice`. When `fetchAppliedChart` populated slice in lockstep, the gate skips. When the user selects a different chart from the dropdown (`handleSelectValue`), `slice` is now cleared to null first, so the watcher fires and fetches correctly. 2. **Dead `useCallback`.** `renderChartHeader` (empty deps) only built JSX from its arguments and was called inline as `renderChartHeader(…)` — neither the produced node nor the function identity was observed by a memoized consumer, so the useCallback was overhead with no benefit. Inline as a plain helper named `buildChartHeader`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -453,18 +453,28 @@ function AnnotationLayer({
|
||||
string | number | SelectOption | null
|
||||
>(value);
|
||||
|
||||
// componentDidUpdate - fetch slice data when value changes
|
||||
// Fetch slice data when value changes — but skip when slice is already
|
||||
// populated, which means the change came from `fetchAppliedChart` (the
|
||||
// mount-time hydration path that sets both value and slice in lockstep).
|
||||
// The user-selection path (`handleSelectValue`) clears slice to null
|
||||
// before setting the new value, so this watcher fires correctly there.
|
||||
useEffect(() => {
|
||||
if (value !== prevValue) {
|
||||
const isChart =
|
||||
sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE &&
|
||||
requiresQuery(sourceType ?? undefined);
|
||||
if (isChart && value && typeof value === 'object' && 'value' in value) {
|
||||
if (
|
||||
isChart &&
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
'value' in value &&
|
||||
!slice
|
||||
) {
|
||||
fetchSliceData(value.value);
|
||||
}
|
||||
setPrevValue(value);
|
||||
}
|
||||
}, [value, prevValue, sourceType, fetchSliceData]);
|
||||
}, [value, prevValue, sourceType, fetchSliceData, slice]);
|
||||
|
||||
const isValidFormulaAnnotation = useCallback(
|
||||
(
|
||||
@@ -529,6 +539,9 @@ function AnnotationLayer({
|
||||
const handleSelectValue = useCallback(
|
||||
(selectedValueObject: SelectOption): void => {
|
||||
setValue(selectedValueObject);
|
||||
// Clear slice so the value-change watcher refetches for the new chart;
|
||||
// see the watcher block above for why this gate exists.
|
||||
setSlice(null);
|
||||
setDescriptionColumns([]);
|
||||
setIntervalEndColumn('');
|
||||
setTimeColumn('');
|
||||
@@ -618,20 +631,22 @@ function AnnotationLayer({
|
||||
close?.();
|
||||
}, [applyAnnotation, close]);
|
||||
|
||||
const renderChartHeader = useCallback(
|
||||
(
|
||||
label: string,
|
||||
description: string,
|
||||
val: string | number | SelectOption | null,
|
||||
): React.ReactNode => (
|
||||
<ControlHeader
|
||||
hovered
|
||||
label={label}
|
||||
description={description}
|
||||
validationErrors={!val ? ['Mandatory'] : []}
|
||||
/>
|
||||
),
|
||||
[],
|
||||
// Inlined: this is a pure helper that produces JSX from its args. The
|
||||
// previous useCallback wrapper added no value — `renderChartHeader(label,
|
||||
// description, value)` was called inline, so the produced ReactNode was
|
||||
// recreated each call anyway, and no downstream consumer depended on the
|
||||
// function's identity.
|
||||
const buildChartHeader = (
|
||||
label: string,
|
||||
description: string,
|
||||
val: string | number | SelectOption | null,
|
||||
): React.ReactNode => (
|
||||
<ControlHeader
|
||||
hovered
|
||||
label={label}
|
||||
description={description}
|
||||
validationErrors={!val ? ['Mandatory'] : []}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderValueConfiguration = useCallback((): React.ReactNode => {
|
||||
@@ -664,7 +679,7 @@ function AnnotationLayer({
|
||||
key={sourceType}
|
||||
ariaLabel={t('Annotation layer value')}
|
||||
name="annotation-layer-value"
|
||||
header={renderChartHeader(label, description, value)}
|
||||
header={buildChartHeader(label, description, value)}
|
||||
options={fetchOptions}
|
||||
value={value || null}
|
||||
onChange={handleSelectValue}
|
||||
@@ -699,7 +714,6 @@ function AnnotationLayer({
|
||||
annotationType,
|
||||
value,
|
||||
getSupportedSourceTypes,
|
||||
renderChartHeader,
|
||||
fetchOptions,
|
||||
handleSelectValue,
|
||||
handleTextValue,
|
||||
|
||||
Reference in New Issue
Block a user