apache/echarts · error

key must not be "{}"

Error message

key must not be "{}"

What it means

DEV-only assertion in customGraphicTransition.assertNotReserved. Custom-series graphic elements may carry an 'extra' bag keyed by user-chosen strings, but 'transition', 'enterFrom', and 'leaveTo' are reserved internal keys consumed by the element transition system. Reusing them would collide with the transition machinery, so the dev build rejects them; the assertion is stripped in production.

Source

Thrown at src/animation/customGraphicTransition.ts:424

            || ((tmpDuringScope.el as LooseElementProps).extra = {});
        extra[key] = val;
        return this;
    },
    getExtra(key: string): unknown {
        if (__DEV__) {
            assertNotReserved(key);
        }
        const extra = (tmpDuringScope.el as LooseElementProps).extra;
        if (extra) {
            return extra[key];
        }
    }
};

function assertNotReserved(key: string) {
    if (__DEV__) {
        if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') {
            throw new Error('key must not be "' + key + '"');
        }
    }
}

function duringCall(
    this: {
        el: Element;
        userDuring: (params: TransitionDuringAPI) => void;
    }
): void {
    // Do not provide "percent" until some requirements come.
    // Because consider thies case:
    // enterFrom: {x: 100, y: 30}, transition: 'x'.
    // And enter duration is different from update duration.
    // Thus it might be confused about the meaning of "percent" in during callback.
    const scope = this;
    const el = scope.el;
    if (!el) {

View on GitHub (pinned to 30076aedcd)

Solutions

  1. Rename the reserved key to a non-reserved name (e.g. 'fadeTransition', 'myEnterFrom')
  2. Namespace your extra props to avoid collisions (e.g. extra._myFade)
  3. Prefer the dedicated element transition API over stuffing data into extra

Example fix

// before
el.extra = { transition: 0.5, enterFrom: { opacity: 0 } };

// after
el.extra = { fadeTransition: 0.5, myEnterFrom: { opacity: 0 } };
Defensive patterns

Strategy: type-guard

Validate before calling

const RESERVED = ['transition', 'enterFrom', 'leaveTo'];
Object.keys(el.extra || {}).forEach(k => {
  if (RESERVED.includes(k)) throw new Error('reserved extra key: ' + k);
});

Type guard

const isReservedExtraKey = (k: string): boolean =>
  k === 'transition' || k === 'enterFrom' || k === 'leaveTo';

Prevention

When it happens

Trigger: In a custom series renderItem, setting el.extra = { transition: ... } / { enterFrom: ... } / { leaveTo: ... }, or otherwise indexing an element's extra with one of those reserved names.

Common situations: Authoring custom series with element transitions and accidentally reusing a reserved name; upgrading to a version that newly reserves these keys.

Related errors


AI-assisted analysis of apache/echarts@30076aedcd (2026-08-12). Data as JSON: /api/errors/1143bb996f09b7eb. Report an issue: GitHub.