{"record":{"id":"1143bb996f09b7eb","repo":"apache/echarts","slug":"key-must-not-be","errorCode":null,"errorMessage":"key must not be \"{}\"","messagePattern":"key must not be \"(.+?)\"","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/animation/customGraphicTransition.ts","lineNumber":424,"sourceCode":"            || ((tmpDuringScope.el as LooseElementProps).extra = {});\n        extra[key] = val;\n        return this;\n    },\n    getExtra(key: string): unknown {\n        if (__DEV__) {\n            assertNotReserved(key);\n        }\n        const extra = (tmpDuringScope.el as LooseElementProps).extra;\n        if (extra) {\n            return extra[key];\n        }\n    }\n};\n\nfunction assertNotReserved(key: string) {\n    if (__DEV__) {\n        if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') {\n            throw new Error('key must not be \"' + key + '\"');\n        }\n    }\n}\n\nfunction duringCall(\n    this: {\n        el: Element;\n        userDuring: (params: TransitionDuringAPI) => void;\n    }\n): void {\n    // Do not provide \"percent\" until some requirements come.\n    // Because consider thies case:\n    // enterFrom: {x: 100, y: 30}, transition: 'x'.\n    // And enter duration is different from update duration.\n    // Thus it might be confused about the meaning of \"percent\" in during callback.\n    const scope = this;\n    const el = scope.el;\n    if (!el) {","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/apache/echarts/blob/30076aedcd7b7f65d8dd8e8d9ece46ce778133a3/src/animation/customGraphicTransition.ts#L406-L442","documentation":"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.","triggerScenarios":"In a custom series renderItem, setting el.extra = { transition: ... } / { enterFrom: ... } / { leaveTo: ... }, or otherwise indexing an element's extra with one of those reserved names.","commonSituations":"Authoring custom series with element transitions and accidentally reusing a reserved name; upgrading to a version that newly reserves these keys.","solutions":["Rename the reserved key to a non-reserved name (e.g. 'fadeTransition', 'myEnterFrom')","Namespace your extra props to avoid collisions (e.g. extra._myFade)","Prefer the dedicated element transition API over stuffing data into extra"],"exampleFix":"// before\nel.extra = { transition: 0.5, enterFrom: { opacity: 0 } };\n\n// after\nel.extra = { fadeTransition: 0.5, myEnterFrom: { opacity: 0 } };","handlingStrategy":"type-guard","validationCode":"const RESERVED = ['transition', 'enterFrom', 'leaveTo'];\nObject.keys(el.extra || {}).forEach(k => {\n  if (RESERVED.includes(k)) throw new Error('reserved extra key: ' + k);\n});","typeGuard":"const isReservedExtraKey = (k: string): boolean =>\n  k === 'transition' || k === 'enterFrom' || k === 'leaveTo';","tryCatchPattern":null,"preventionTips":["Prefix custom extra keys with a namespace","Run a dev build during development to surface these assertions","Consult the reserved-key list in customGraphicTransition before naming extra props"],"tags":["custom-series","transitions","reserved-keys","dev-only"],"backgroundTag":null,"analyzedSha":"30076aedcd7b7f65d8dd8e8d9ece46ce778133a3","analyzedAt":"2026-08-12T12:42:28.134Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}