Svelte: Highliting a table row using class directives does not work

166 Views Asked by At

I recently started to look into Svelte and wanted to create a component called SelectorTable which allows me to select the rows of table by clicking on them. A selected row is supposed to get highlighted in a different color.

I tried using the build in class directive.

<script lang="ts">
    type ObjectKey = keyof any; 
    export let data: any[] = [];
    export let key = 'id' as ObjectKey;
    let selected: Set<ObjectKey> = new Set<ObjectKey>();


    function toggle(id: ObjectKey) {
        if (selected.has(id)) {
            selected.delete(id);
        } else {
            selected.add(id)
        }
        console.log(selected)
        console.log(selected.has(id))
    }
</script>

<table>
    <thead>
        <tr>
            {#each Object.keys(data[0]) as heading}
            <th>{heading}</th>
            {/each}
        </tr>
    </thead>
    <tbody>
        {#each Object.values(data) as row}
            <tr class:tr-selected={selected.has(row[key])} on:click={() => {toggle(row[key])}}>
                {#each Object.values(row) as cell}
                <td>{cell}</td>
                {/each}
            </tr>
        {/each}
    </tbody>
</table>

<style>
    table, th, td {
        border: 1px solid;
        border-collapse: collapse;
        margin-bottom: 10px;
    }
    tr:hover {
        background-color: red;
        cursor: pointer;
    }

    .tr-selected { 
        background-color: greenyellow;
    }
</style>

The on:click function toggle works and correctly adds or removes the selected keys. The problem I have is that the background color of the selected rows are not changing even if selected.has(row[key]) == true. Here is the console output when clicking on a row with the key 1 twice.

> Set(1) {1} ------- SelectorTable.svelte:14 
> true ------------- SelectorTable.svelte:15
> Set(0) {size: 0} - SelectorTable.svelte:14
> false ------------ SelectorTable.svelte:15

Firstly the key is added to the selected set and then it is removed again.

So if the output of selected.has(row[key]) is true why is the class directive not working and the color of the row not changing?

2

There are 2 best solutions below

1
On BEST ANSWER

I have found a solution. Not sure if there is a better one but this one works.

After trying out a bit I realized that Svelte was not checking selected.has(row[key]) in the class directive and was therefore not rerendering the row. I then tried sveltes key logic block by passing the selected set into it but it still did not work.

Lastly I added a new boolean variable update which I reference in the key block and every time the function toggle is called update gets flipped.

<script lang="ts">
    type ObjectKey = keyof any; 
    export let data: any[] = [];
    export let key = 'id' as ObjectKey;
    let selected: Set<ObjectKey> = new Set<ObjectKey>();

    // Add update variable
    let update: boolean = false

    function toggle(id: ObjectKey) {
        // Flip updates value to indicate a change
        update = !update
        if (selected.has(id)) {
            selected.delete(id);
        } else {
            selected.add(id)
        }
    }
</script>

<table>
    <thead>
        <tr>
            {#each Object.keys(data[0]) as heading}
            <th>{heading}</th>
            {/each}
        </tr>
    </thead>
    <tbody>
        {#each Object.values(data) as row}
            <!-- Reference update to rerender the row when its value changes -->
            {#key update}
                <tr class:tr-selected={selected.has(row[key])} on:click={() => {toggle(row[key])}}>
                    {#each Object.values(row) as cell}
                    <td>{cell}</td>
                    {/each}
                </tr>
            {/key}
        {/each}
    </tbody>
</table>

<style>
    table, th, td {
        border: 1px solid;
        border-collapse: collapse;
        margin-bottom: 10px;
    }
    tr:hover {
        background-color: red;
        cursor: pointer;
    }

    .tr-selected { 
        background-color: greenyellow;
    }
</style>

Correct me if I am wrong.

I assume the reason for this behaviour is that JS/TS passes objects and functions like the selected or toggle by reference and primitive variables like ints or booleans by value.

Sveltes documentation of key states

"Key blocks destroy and recreate their contents when the value of an expression changes."

This would explain why the row did not rerender when using selected. Because it is passed by reference and references are primitive types as well and since only the value behind the reference changed and not the reference itself Svelte does not recognize the change and therefor does not rerender.

This same behaviour is not stated specifically in the docs of class directives but it's save to assume that it will be similar.

0
On

Reactivity is based on assignments, so if you use something like a Set which is modified via functions, you can just add a dummy assignment after the change to trigger updates wherever the set is being used:

function toggle(id: ObjectKey) {
    if (selected.has(id)) {
        selected.delete(id);
    } else {
        selected.add(id)
    }

    selected = selected; // <--
}