Search code examples
javascriptsveltesvelte-3svelte-component

How to keep a reference to component created within a loop?


I'm learning Svelte and I'm trying to make a component (a color palette). I blindly went for a solution where each ColorSelector (the color div you click on) is a Svelte component. (I'd gladly take an example where no child components are used)

I export a selected property in the ColorSelector.svelte component file. I'd like to set that property to false on every ColorSelectors instantiated when one of them is clicked except on the one that has been clicked.

However, I'm struggling to find how to keep a reference to an instantiated component in a loop. How can I achieve this?

<script lang="ts">
  import { Colors, Color } from "./modules/colors";
  import ColorSelector from "./ColorSelector.svelte";

  const DEFAULT_COLOR = Colors.LIGHT_WHITE;
  let selectedColor:Color = DEFAULT_COLOR;

function handleClick(event, i) {
  selectedColor = event.detail.color;
  // When clicked set ColorSelector.selected = false on evert ColorSelectors 
  // except the one that has been clicked
}
</script>

<div>
  {#each Object.values(Colors) as color, i}
    <ColorSelector on:selected={handleSelect} color={color}></ColorSelector>
  {/each}
</div>

<style>
  div {
    display: flex;
  }
</style>


Solution

  • To keep a reference to a component inside a loop you could use bind:this to an array. Have a look at this question Svelte how to bind div inside each lop to obtain a reference using this

    To be able to set the property on the component the option must be activated with <svelte:options accessors={true}/> Here's a solution with the data flow I assume you were trying to build >> REPL

    <script>
        import ColorSelector from './ColorSelector.svelte'
        import {Colors} from './Colors'
        const DEFAULT_COLOR = Colors.LIGHT_WHITE;
    
        let selectedColor = DEFAULT_COLOR
        let colorSelectors = []
    
        function handleSelect(e,i) {
            selectedColor = e.detail.color
            colorSelectors.forEach(cS => cS.selected=false)
            colorSelectors[i].selected = true
        }
    
    </script>
    
    <p><b>{selectedColor}</b></p>
    
    <div>
        {#each Object.values(Colors) as color, i}
        <ColorSelector {color} bind:this={colorSelectors[i]}
                               selected={color === DEFAULT_COLOR ? true : false}
                               on:selected="{(e) => handleSelect(e,i)}"/>
        {/each}
    </div>
    
    <style>
        div {
            display: flex;
            flex-wrap: wrap;
            box-sizing: border-box;
        }
    </style>
    

    [ColorSelector.svelte]

    <svelte:options accessors={true}/>
    
    <script>
        import { createEventDispatcher } from 'svelte';
        const dispatch = createEventDispatcher();
        
        export let color, selected  
    
        function handleClick() {
            dispatch('selected', {
                color
            });
        }
    </script>
    
    <div class="color-selector"
             class:selected
             style:background={color}            
             on:click={handleClick}
        ></div>
    
    <style>
        .color-selector {
            flex-basis: 200px;
            flex-grow: 1;
            height: 100px;
            box-sizing: border-box;
        }
        .selected {
            border: 5px solid black;
        }
    </style>
    
    
    

    But I think these features are not really needed to build the selector nor is the selected = true/false flag because only the one selected color is important. Here's a simpler solution with and without a component REPL

    <script>
        import ColorSelector from './ColorSelector.svelte'
        import {Colors} from './Colors'
        const DEFAULT_COLOR = Colors.LIGHT_WHITE;
        let selectedColor = DEFAULT_COLOR;
    </script>
    
    <p><b>{selectedColor}</b></p>
    
    <div>
        {#each Object.values(Colors) as color, i}
        <div class="color-selector"
                 class:selected-color={selectedColor === color}
                 style:background={color}            
                 on:click={() => selectedColor = color}
            ></div>
        {/each}
    </div>
    
    <hr>
    
    <div>
        {#each Object.values(Colors) as color, i}
        <ColorSelector {color} bind:selectedColor />
        {/each}
    </div>
    
    <style>
        div {
            display: flex;
            flex-wrap: wrap;
            box-sizing: border-box;
        }
        .color-selector {
            flex-basis: 200px;
            flex-grow: 1;
            height: 100px;
        }
        .selected-color {
            border: 5px solid black;
        }
        hr {
            margin: 3rem;
        }
    </style>
    

    [ColorSelector.svelte]

    <script>
        export let color, selectedColor
    </script>
    
    <div class="color-selector"
             class:selected-color={selectedColor === color}
             style:background={color}            
             on:click={() => selectedColor = color}
        ></div>
    
    <style>
        .color-selector {
            flex-basis: 200px;
            flex-grow: 1;
            height: 100px;
            box-sizing: border-box;
        }
        .selected-color {
            border: 5px solid black;
        }
    </style>