Search code examples
server-side-renderingsveltesapper

How do you write a reactive statement in Svelte/Sapper when it needs to access and update a DOM element?


I am trying to write a function drawChart that is called each time my variable stats changes to update my current chart.

I am able to draw my initial chart when using onMount, but when I fetch new stats, the chart does not update.

I can't figure out how to do this in Sapper because my function also relies on the canvas element to use for my chart's context.

I tried writing a reactive statement such as $: drawChart(chartContext, stats), but chartContext is undefined.

My code:

<script>
    import { onMount } from 'svelte';
    import Chart from 'chart.js';

    export let stats;

    let chartContext;
    let chartCanvas;
    let chart;

    onMount(() => {    
        chartContext = chartCanvas.getContext('2d');

        drawChart(chartContext, stats);
    })

    const drawChart = (chartContext, stats) => {
        const s = fetchStats(stats);

        chart = new Chart(chartContext, {
            type: 'bar',
            data: {
                labels: s.seasons,
                datasets: [{
                    label: s.stat1.label,
                    data: s.stat1.data,
                    backgroundColor: 'whitesmoke',
                    borderColor: 'black',
                    borderWidth: 1
                }]
            },
            options: {
                scales: {
                    yAxes: [{
                        ticks: {
                            beginAtZero: true
                        }
                    }]
                },
                maintainAspectRatio: false,
                responsive: true
            }
        });
    }

    // I want this to be called each time stats is updated, but it does not work as chartContext is undefined here
    $: drawChart(chartContext, stats);
</script>

<canvas id="chart" width="200" height="200" aria-label="chart" role="figure" bind:this={chartCanvas}></canvas>

Is there a way to solve this issue? Any help is greatly appreciated :)


Solution

  • Yes, it is expected that you get a run of the reactive block when chartContext is still undefined. It happens before onMount. Svelte simply runs the reactive block anytime any of the depended variable changes, and this includes their initial values. Svelte doesn't wait for the component to be mounted in the DOM before running reactive blocks or anything like that.

    So you simply need to account for this special case in your code:

    $: if (chartContext) drawChart(chartContext, stats)
    

    Here. All set. (You may also want to ensure that stats is set in the same fashion, to make your component more resilient to what its consumers might possibly send to it.)