Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

state_descriptors_fixed occurs when object is enumerable, configurable and writable and has value #15607

Closed
fel1x-developer opened this issue Mar 25, 2025 · 5 comments
Labels
awaiting submitter needs a reproduction, or clarification

Comments

@fel1x-developer
Copy link

Describe the bug

I'm trying to make a wrapper for Chart.js. When there is data is passed as plain non-$state prop, it works fine. But when data is defined to be reactive with $state, the chart doesn't error due to the following error in the log section. This causes problem in Vitest and Storybook, making them unusable.

Reproduction

Clone https://github.com/sveltejs-labs/Chart.js and run npm run dev with browser developer console opened. It will print the properties of data object and print

Unhandled Promise Rejection: Svelte error: state_descriptors_fixed
Property descriptors defined on `$state` objects must contain `value` and always be `enumerable`, `configurable` and `writable`.

Logs

Chart.js on  main [$] is 📦 1.1.0 via ⬢ v22.14.0 
➜ bun run dev
$ vite dev

  VITE v6.2.3  ready in 1301 ms

  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
  ➜  press h + enter to show help
{
  labels: {
    value: [
      'January', 'February',
      'March',   'April',
      'May',     'June',
      'July'
    ],
    writable: true,
    enumerable: true,
    configurable: true
  },
  'labels.0': {
    value: 'January',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'labels.1': {
    value: 'February',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'labels.2': {
    value: 'March',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'labels.3': {
    value: 'April',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'labels.4': {
    value: 'May',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'labels.5': {
    value: 'June',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'labels.6': {
    value: 'July',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'labels.length': { value: 7, writable: true, enumerable: false, configurable: false },
  datasets: {
    value: [ [Object] ],
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0': {
    value: {
      label: 'My First Dataset',
      data: [Array],
      backgroundColor: [Array],
      borderColor: [Array],
      borderWidth: 1
    },
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.label': {
    value: 'My First Dataset',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.data': {
    value: [
      65, 59, 80, 81,
      56, 55, 40
    ],
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.data.0': { value: 65, writable: true, enumerable: true, configurable: true },
  'datasets.0.data.1': { value: 59, writable: true, enumerable: true, configurable: true },
  'datasets.0.data.2': { value: 80, writable: true, enumerable: true, configurable: true },
  'datasets.0.data.3': { value: 81, writable: true, enumerable: true, configurable: true },
  'datasets.0.data.4': { value: 56, writable: true, enumerable: true, configurable: true },
  'datasets.0.data.5': { value: 55, writable: true, enumerable: true, configurable: true },
  'datasets.0.data.6': { value: 40, writable: true, enumerable: true, configurable: true },
  'datasets.0.data.length': { value: 7, writable: true, enumerable: false, configurable: false },
  'datasets.0.backgroundColor': {
    value: [
      'rgba(255, 99, 132, 0.2)',
      'rgba(255, 159, 64, 0.2)',
      'rgba(255, 205, 86, 0.2)',
      'rgba(75, 192, 192, 0.2)',
      'rgba(54, 162, 235, 0.2)',
      'rgba(153, 102, 255, 0.2)',
      'rgba(201, 203, 207, 0.2)'
    ],
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.backgroundColor.0': {
    value: 'rgba(255, 99, 132, 0.2)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.backgroundColor.1': {
    value: 'rgba(255, 159, 64, 0.2)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.backgroundColor.2': {
    value: 'rgba(255, 205, 86, 0.2)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.backgroundColor.3': {
    value: 'rgba(75, 192, 192, 0.2)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.backgroundColor.4': {
    value: 'rgba(54, 162, 235, 0.2)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.backgroundColor.5': {
    value: 'rgba(153, 102, 255, 0.2)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.backgroundColor.6': {
    value: 'rgba(201, 203, 207, 0.2)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.backgroundColor.length': { value: 7, writable: true, enumerable: false, configurable: false },
  'datasets.0.borderColor': {
    value: [
      'rgb(255, 99, 132)',
      'rgb(255, 159, 64)',
      'rgb(255, 205, 86)',
      'rgb(75, 192, 192)',
      'rgb(54, 162, 235)',
      'rgb(153, 102, 255)',
      'rgb(201, 203, 207)'
    ],
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.borderColor.0': {
    value: 'rgb(255, 99, 132)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.borderColor.1': {
    value: 'rgb(255, 159, 64)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.borderColor.2': {
    value: 'rgb(255, 205, 86)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.borderColor.3': {
    value: 'rgb(75, 192, 192)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.borderColor.4': {
    value: 'rgb(54, 162, 235)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.borderColor.5': {
    value: 'rgb(153, 102, 255)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.borderColor.6': {
    value: 'rgb(201, 203, 207)',
    writable: true,
    enumerable: true,
    configurable: true
  },
  'datasets.0.borderColor.length': { value: 7, writable: true, enumerable: false, configurable: false },
  'datasets.0.borderWidth': { value: 1, writable: true, enumerable: true, configurable: true },
  'datasets.length': { value: 1, writable: true, enumerable: false, configurable: false }
}

System Info

System:
    OS: macOS 15.3.2
    CPU: (8) arm64 Apple M2
    Memory: 655.97 MB / 24.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.14.0 - ~/.local/state/fnm_multishells/75553_1742872528675/bin/node
    Yarn: 1.22.22 - ~/.local/state/fnm_multishells/75553_1742872528675/bin/yarn
    npm: 10.9.2 - ~/.local/state/fnm_multishells/75553_1742872528675/bin/npm
    bun: 1.2.5 - ~/.bun/bin/bun
  Browsers:
    Chrome: 134.0.6998.165
    Safari: 18.3.1
  npmPackages:
    svelte: ^5.25.3 => 5.25.3

Severity

blocking all usage of svelte

@paoloricciuti
Copy link
Member

Please provide a minimal reproduction, your whole project is not minimal and it would take us much more time than you to create a minimal one we can work on

@paoloricciuti paoloricciuti added the awaiting submitter needs a reproduction, or clarification label Mar 25, 2025
@fel1x-developer
Copy link
Author

Please provide a minimal reproduction, your whole project is not minimal and it would take us much more time than you to create a minimal one we can work on

<script lang="ts">
    import { onMount, onDestroy } from 'svelte';
    import Chart, { registerables } from 'chart.js/auto';
    import months from '../../stories/utils/months.js';
    import type { ChartData, ChartOptions } from 'chart.js';

    const type = 'bar';

    const labels = months({ count: 7 });
    const data: ChartData = $state({
        labels: labels,
        datasets: [
            {
                label: 'My First Dataset',
                data: [65, 59, 80, 81, 56, 55, 40],
                backgroundColor: [
                    'rgba(255, 99, 132, 0.2)',
                    'rgba(255, 159, 64, 0.2)',
                    'rgba(255, 205, 86, 0.2)',
                    'rgba(75, 192, 192, 0.2)',
                    'rgba(54, 162, 235, 0.2)',
                    'rgba(153, 102, 255, 0.2)',
                    'rgba(201, 203, 207, 0.2)'
                ],
                borderColor: [
                    'rgb(255, 99, 132)',
                    'rgb(255, 159, 64)',
                    'rgb(255, 205, 86)',
                    'rgb(75, 192, 192)',
                    'rgb(54, 162, 235)',
                    'rgb(153, 102, 255)',
                    'rgb(201, 203, 207)'
                ],
                borderWidth: 1
            }
        ]
    });

    const options: ChartOptions = {
        scales: {
            y: {
                beginAtZero: true
            }
        }
    };

    const updateMode = 'default';

	let canvasRef: HTMLCanvasElement | null = $state(null);
	let chart: Chart | null = null;

	onMount(() => {
		Chart.register(...registerables);

		if (canvasRef) {
			chart = new Chart(canvasRef, {
				type,
				data,
				options
			});
		}
	});

	$effect(() => {
		if (!chart) return;

		chart.data = data;
		Object.assign(chart.options, options);
		chart.update(updateMode);
	});

	onDestroy(() => {
		if (chart) chart.destroy();
		chart = null;
	});
</script>

<canvas bind:this={canvasRef}></canvas>

<style>
	canvas {
		max-width: 100%;
	}
</style>

@fel1x-developer
Copy link
Author

The only case where I see enumerable and configurable are false is the length property of an array in object, but length always exists for arrays.

@paoloricciuti
Copy link
Member

Image

Chart.js is defining properties on it when initialising...you can snapshot data to fix this issue

<script lang="ts">
    import { onMount, onDestroy } from 'svelte';
    import Chart, { registerables } from 'chart.js/auto';
    import months from '../../stories/utils/months.js';
    import type { ChartData, ChartOptions } from 'chart.js';

    const type = 'bar';

    const labels = months({ count: 7 });
    const data: ChartData = $state({
        labels: labels,
        datasets: [
            {
                label: 'My First Dataset',
                data: [65, 59, 80, 81, 56, 55, 40],
                backgroundColor: [
                    'rgba(255, 99, 132, 0.2)',
                    'rgba(255, 159, 64, 0.2)',
                    'rgba(255, 205, 86, 0.2)',
                    'rgba(75, 192, 192, 0.2)',
                    'rgba(54, 162, 235, 0.2)',
                    'rgba(153, 102, 255, 0.2)',
                    'rgba(201, 203, 207, 0.2)'
                ],
                borderColor: [
                    'rgb(255, 99, 132)',
                    'rgb(255, 159, 64)',
                    'rgb(255, 205, 86)',
                    'rgb(75, 192, 192)',
                    'rgb(54, 162, 235)',
                    'rgb(153, 102, 255)',
                    'rgb(201, 203, 207)'
                ],
                borderWidth: 1
            }
        ]
    });

    const options: ChartOptions = {
        scales: {
            y: {
                beginAtZero: true
            }
        }
    };

    const updateMode = 'default';

	let canvasRef: HTMLCanvasElement | null = $state(null);
	let chart: Chart | null = null;

	onMount(() => {
		Chart.register(...registerables);

		if (canvasRef) {
			chart = new Chart(canvasRef, {
				type,
-				data,
+				data: $state.snapshot(data),
				options
			});
		}
	});

	$effect(() => {
		if (!chart) return;

		chart.data = data;
		Object.assign(chart.options, options);
		chart.update(updateMode);
	});

	onDestroy(() => {
		if (chart) chart.destroy();
		chart = null;
	});
</script>

<canvas bind:this={canvasRef}></canvas>

<style>
	canvas {
		max-width: 100%;
	}
</style>

you might need to snapshot here and there in case you find this issue when updating. However pay attention because this code has a bug:

$effect(() => {
	if (!chart) return;

	chart.data = data;
	Object.assign(chart.options, options);
	chart.update(updateMode);
});

chart is not stateful here so if it's null the effect will have no dependency and never rerun

@fel1x-developer
Copy link
Author

Thank you! That solved the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting submitter needs a reproduction, or clarification
Projects
None yet
Development

No branches or pull requests

2 participants