Chart.js
Chart.js copied to clipboard
Too complex union type when combining TooltipModel with Vue ref
Expected Behavior
You should be able to use the TooltipModel
type in combination with Vue refs without type issues.
Current Behavior
You get a "Expression produces a union type that is too complex to represent." error in version 3.5.1 and above. 3.5.0 and below works.
Possible Solution
I'm guessing the change that introduced the issue was https://github.com/chartjs/Chart.js/pull/9490, though. Possibly because it made TooltipModel
recursive (TooltipModel -> TooltipItem -> Chart -> TooltipModel)? But to be honest I'm not quite sure if this is something that should be fixed on Chart.js' end, Vue's end or even Typescript's end. If you don't feel like it should be fixed here then feel free to close this (but if you have a suggestion for who to pass the buck to in that case that would be appreciated).
Steps to Reproduce
https://stackblitz.com/edit/vitejs-vite-ynfase?file=src%2Fmain.ts&terminal=dev
You'll have to wait for a while, but eventually Typescript gives up evaluating and the foo.value
will be marked with the error I mentioned.
Note that the {} as ...
stuff is just to simplify the example so I don't have to type out a full actual TooltipModel
, the result is the same either way.
Context
I'm storing a TooltipModel
in a Vue ref because I'm using a Vue component to create a custom tooltip. It's probably possible for me to work around this issue but perhaps not with full type safety intact, which would be a bit of a shame.
Environment
- Chart.js version: 3.6.2 for the example, but in general 3.5.1+ exhibits the issue
- Browser name and version: Chromium 96
- Link to your project: My actual project isn't public, but I've made a local reproduction equivalent to the online one: https://github.com/henribru/chart-js-too-complex-union-type-repro
Can confirm this bug, had to downgrade to 3.5.0
to be able to use the package
A possible workaround is to use shallowRef
for the tooltipModel
. Note that this removes deep reactivity, but that's okay if you're not patching, but reassigning the tooltipModel.value
every time you're rendering your custom tooltip.
https://vuejs.org/api/reactivity-advanced.html#shallowref
Here's a small modified example of how we're rendering a vue component as a tooltip:
<script setup lang="ts">
const tooltipModel = shallowRef<TooltipModel<"line"> | undefined>(undefined);
// ... in chartOptions
plugins: {
tooltip: {
enabled: false,
external: function ({ tooltip }: { tooltip: TooltipModel<"line"> }) {
if (tooltip.opacity) {
// works with shallowRef
tooltipModel.value = { ...tooltip };
// doesn't work with shallowRef
// tooltipModel.value.someProperty = tooltip.someProperty;
showToolTip.value = true;
if (timeoutId) {
clearInterval(timeoutId);
}
} else {
timeoutId = setTimeout(hideToolTip, 200);
}
},
},
},
</script>
<template>
<div class="relative h-full w-full" :style="graphHeight">
<CustomChartTooltip
:show-tool-tip="showToolTip"
:tooltip-model-ref="tooltipModel"
/>
<canvas v-once :id="canvasId" />
</div>
</template>
Possibly because it made TooltipModel recursive (TooltipModel -> TooltipItem -> Chart -> TooltipModel)
This assumption and solution kinda makes sense (I think) when looking at the type definitions for how shallowRef
s are unwrapped compared to ref
s. Involves some recursion stuff I don't want to spend too much time trying to understand 😄
@BeeeQueue