Search code examples
javascriptsveltevirtualscroll

How to persist state of Svelte components in a virtual list?


I'm developing a gantt chart component in Svelte and it consists of 100s of row components each containing multiple task components each. Tasks can be dragged and moved. Performance is a priority and I am using a virtual list to render only the visible rows.

<div class="scrollable">
    {#each visibleRows as row (row.id)}
        <div class="row">
            {#each row.tasks as task (task.id)}
                <Task from={task.from} to={to}/>
            {/each}
        </div>
    {/each}
</div>

The problem is when I move tasks around and scroll rows out of the view, they get destroyed, and when I scroll back to them, their state gets reset. This REPL using svelte-virtual-list illustrates the problem https://svelte.technology/repl?version=2.13.5&gist=bdb4c523d2e1cf7e3aef452e3f24d988 I am wondering what is the best way to deal with this situation.

Currently I'm using the task object as prop and updating it, the reference keeps the same and scrolling doesn't affect it.

<Task {task}/>

But inside Task I update state like this

const {task} = this.get();
task.from = new Date();
this.set({task});

and the template references every prop with task.propname and I'm not sure if it's the svelte way of doing it as it is not clear what exactly gets set.

I could bind task variables like this

<Task bind:from=task.from 
      bind:to={task.to}/>

But a lot of variables need to be bind (more than 10), and I want to allow possible future plugins update new props from inside the Task component.

<Task {task} 
      {...task}/>

This deals with potential bigger number of props, but I update the task object in the state event like this:

onstate({ changed, current, previous }) {
    const {task} = this.get();
    Object.assign(task, current);
    this.set({task});
},

Which of these should I prefer for performance or is there a better way to handle this?


Solution

  • You should alter the array of data instead of mutating the object. By mutating the object inside the array, Svelte does not know that your array has changed.

    Mutating the array would go something like this with Svelte v3 and using a store instead of plain-array:

    // data.js
    ...
    import { writable } from 'svelte/store';
    
    export default writable(getData())
    ...
    
    // App.svelte
    <script>
    ...
    import items from './data.js';
    
    function updateContent(item) {
        const index = $items.indexOf(item)
        $items = [
            ...$items.slice(0, index),
            { ...item, content: 'updated content' },
            ...$items.slice(index + 1)
        ]
    }
    ...
    </script>
    
    <VirtualList items={$items} let:item bind:start bind:end>
        ...
            <button on:click={() => updateContent(item)}>update content</button>
        ...
    </VirtualList>
    

    Full example: https://svelte.dev/repl?version=3.0.0-beta.28&gist=f8c7e96bb34906db2c3a5e85b4840017

    You can read more about updating arrays and objects here: https://svelte.dev/tutorial/updating-arrays-and-objects