roydukkey
roydukkey

Reputation: 3288

Is it possible to bind the same variable across multiple components with Svelte?

If the same component is used multiple times from the same context, is it possible for the bound property to be shared across their instances?

For instance, if I have a component that creates checkboxes, how can the selection (bind:group) be combined for the letter selector and the number selector?

With this example, if multiple letters are selected, the selection is properly propagated and bound. However, if numbers are then selected, the letter selection is replace with the selected numbers, instead of joining the selected numbers to the selected letters.

// App.svelte
<script>
    import Selector from './Selector.svelte';

    let selection = [];

    $: console.log(selection);
</script>

<h2>Letters</h2>
<Selector options={['A', 'B', 'C']} bind:selection />

<h2>Numbers</h2>
<Selector options={[1, 2, 3]} bind:selection />

// Selector.svelte
<script>
    export let options;
    export let selection;
</script>


<div class="selector">
    {#each options as option}
    <label>
        <input type="checkbox" value={option} bind:group={selection} />
                {option}
    </label>
    {/each}
</div>

REPL: https://svelte.dev/repl/f97f859ea567473b9732b8933db870f7?version=3.20.1

Upvotes: 4

Views: 5340

Answers (1)

rixo
rixo

Reputation: 25001

Yes and no.

Actually what you've done does bind the variable across the various instance of the component. The object is the same, ===, with the same reference... Until this happens:

        selection = value;

This is from the compiled code in your REPL. What happens when you click on a checkbox is that Svelte creates a new array containing the checked values, and assign it to the bind:group variable. At this point, the variable is still the same... But it's content has been replaced with a new array reference. That's why when you change group, you lose the value of the previous one.

You can prove this to yourself by updating your REPL to make selection an object instead of an array, and add some logic to store the value by key.

For example, in App.svelte, change selection to an object:

    let selection = {};

And in Selector.svelte, bind to an intermediary variable and write only to props of the object (not the object itself):

<script>
    export let options;
    export let selection;

    let group = []
    $: for (const option of options) {
        selection[option] = group.includes(option)
    }
</script>


<div class="selector">
    {#each options as option}
    <label>
        <input type="checkbox" value={option} bind:group />
                {option}
    </label>
    {/each}
</div>

Updated REPL

Print the value of selection somehow, and you'll see that it doesn't reset, and contains the value from all the selectors.

So, back to your issue, the problem is really the assignment. And there's not a lot you can do about it directly. There's nothing in Svelte to merge bound values... However, now that we have understood the problem, we can work around it.

The solution is to avoid binding directly the public prop (i.e. selection) but instead bind to an intermediary variable, like in our previous example, and do the merge ourselves somehow (lot of hows available here).

Here's one way you can rewrite your Selector component to accept variable sharing:

<script>
  export let options;
  export let selection;

    let group = []

    const update = () => {
      selection = selection
        .filter(x => !options.includes(x))
        .concat(group)
    }

    // when group changes, update
    $: group, update()
</script>

<div class="selector">
  {#each options as option}
    <label>
      <input type="checkbox" value={option} bind:group />
          {option}
    </label>
  {/each}
</div>

REPL

Note: I didn't put the selection = selection.filter(x => !options.includes(x)).concat(group) expression directly into the reactive block, because our update would then trigger when selection changes. And since it's shared, it will change from the outside. Actually, this would be an infinite loop condition, but Svelte has a protection against this specifically (for reactive blocks). But it's best not to rely on that, and also avoid wasting. Here it will trigger only when our local group changes, which is just what we need.

Upvotes: 5

Related Questions