0

I have a select on car name which displays the car colour. When I input a car in the car field and it exists in the car list, I display the colour in Maintain (this works)

If I change the colour on the currently selected car and save the change, I want the updated colour to appear in the input box of the principle child (Cars)

It works if I re-select the car, but I want it to update as soon as the save button is clicked.

How can I get the colour to update on save?

Here is the REPL

App.svelte

<script>
    import Cars from './Cars.svelte'
    import MaintainCar from './MaintainCar.svelte'
    let cars = {'Audi': 'red', 'BMW':'blue', 'Hillman': 'green'}

    function maintenance() {
        console.log(cars)
    }
</script>

<main>
    <Cars cars={cars}/>
    <MaintainCar cars={cars} on:message={maintenance} />
</main>

Cars.svelte

<script>
    export let cars
    export let selected_car = ''
    let colour = ''
    let car_list = getCars(cars)
    $: colour = cars[selected_car]

    function getCars(cars) {
        let car_list = []
        for (const [key, value] of Object.entries(cars)) {
            car_list.push(key);
        }
        if (!selected_car) {
            selected_car = car_list[0]
            colour = cars[selected_car]
        }
        return car_list
    }

    function selectCar() {
        colour = cars[selected_car]
    }
</script>

<select bind:value={selected_car} on:click={selectCar}>
    {#each car_list as car}
        <option value={car}>
            {car}
        </option>
    {/each}
</select>
<input type="text" bind:value={colour} placeholder="colour"/>

MaintainCar.svelte

<div>
    <input type="text" bind:value={car} placeholder="car"/>
        <input type="text" bind:value={colour} placeholder="colour"/>
    <div>
        <button class="select-button" on:click={saveData}>Save</button>
    </div>
</div>

<script>
    import { createEventDispatcher } from 'svelte';
    const dispatch = createEventDispatcher();
    export let cars
    let car = ''
    $: colour = checkCar(car)

    function checkCar(car) {
        colour = '';
        if (car && car in cars) {
            colour = cars[car];
        }
        return colour
    }

    function saveData() {
        cars[car] = colour;
        car = ''
        colour = ''
        dispatch('message', {
            text: 'colour'
        });
    }
</script>
Psionman
  • 3,084
  • 1
  • 32
  • 65
  • Does this answer your question? [Passing data between sibling components in Svelte](https://stackoverflow.com/questions/63653518/passing-data-between-sibling-components-in-svelte) – Stephane Vanraes Mar 28 '22 at 12:36
  • Here's a bit simplified [REPL](https://svelte.dev/repl/93f54c586b9a4227a19a9009f5d26dd3?version=3.46.4) of your example with the already mentioned binding – Corrl Mar 28 '22 at 13:18
  • That works - but what about the caveats about two way binding made by @rixo below? – Psionman Mar 28 '22 at 13:27
  • With the binding you can edit the passed value from the child - but with the store you can and do this as well ~ so you should probably be cautious in any case :-) (Don't see this as an argument...) I almost never use stores inside components, since you don't need it because the reactivity is given without (like you see in my example) - I might consider making it a (custom) store sitting in an own seperate file so the data is clearer seperated from the components (importing it in the components, since the parent doesn't need it in your case) – Corrl Mar 28 '22 at 13:43
  • Separate stores can totally make sense indeed. My store example was a minimal change of the original example, to better illustrate the point and avoid too much a learning gap to close at once... That said, beware with direct imports of stores from global state / the root scope of another module: they can come bite you hard with SSR (see this ongoing discussion for example: https://github.com/sveltejs/kit/discussions/4339). – rixo Mar 28 '22 at 13:57
  • [Here's](https://svelte.dev/repl/e5ea73d8b09d4acb998134606d6d19e5?version=3.46.4) how this could be done with a [custom store](https://svelte.dev/tutorial/custom-stores) – Corrl Mar 28 '22 at 14:00
  • @rixo Thanks for all your help guys - I now have a separate store and it works! – Psionman Mar 28 '22 at 14:04

1 Answers1

2

Well, if you want the info to descend one way, and come back the other way, you need... two-way binding!

Change:

<MaintainCar cars={cars} on:message={maintenance} />

to:

<MaintainCar bind:cars={cars} on:message={maintenance} />

Now this comes with a lot of cautionary notes...

First, it works only if, in the child component, you're assigning to the exact same variable that was bound from the outside. Like you happen to do now, but it's easy to lose this, as you extract an item from an array, a prop from an object or whatever. To sum it up, you have to be careful about what you do in the child component to support the way it is used from the outside, which is rarely a great situation.

A second warning is about two-way binding in general. While it can be very useful and save a lot of boilerplate in some situations, it can also lead to building a confusing network of hard to track dependencies, going up and down... So, use with care.

A generally more sound approach would be to use a store, that carries reactivity with itself, rather than relying on fragile and tricky chains of two-way bindings. Svelte makes that easy for you!

You could change your cars reactive variable in your parent component:

<script>
  const cars = writable({'Audi': 'red', 'BMW':'blue', 'Hillman': 'green'})

  // don't forget to prefix it with $ to access its _value_
  console.log($cars)
  ...
</script>

<!-- pass the store directly to children (not the value) -->
<Cars {cars}/>
<MaintainCar {cars} on:message={maintenance} />

Then the children can easily both access the value inside the store, or change it, with the changes being propagated back to any place that uses the store:

<script>
  export let cars

  function saveData() {
    $cars[car] = colour;
  }

  ...
</script>

...

See this REPL with your example converted to store.

rixo
  • 23,815
  • 4
  • 63
  • 68
  • Thanks for the advice - I had read about stores but wasn't sure how to apply it. When I implement your code, in Cars I get cars = {set: ƒ, update: ƒ, subscribe: ƒ} – Psionman Mar 28 '22 at 13:20
  • That means you're accessing the store itself instead of its value. You need to prefix the store variable with $ to access the value. See this version of your REPL: https://svelte.dev/repl/67b380036475491dbbed2e28f7ead129?version=3.46.4 – rixo Mar 28 '22 at 13:28
  • Learnt more about stores - now for the docs - will investigate a separate store as advised by @Corri – Psionman Mar 28 '22 at 13:47