0

I'm making a swimming app and I want a button that adds an event to a swim meet. In place of a store, I'm using an object called STATE that acts as a single source of truth for the entire app.

<script>
    import Event from '@shared/models/Event';
    import { STATE, addEventToMeet } from '@src/state/state.svelte.js';
    import EventEditor from './EventEditor.svelte';

    let meet = $state(STATE.meet);
    let events = $derived(STATE.meet.events);
    let lastEvent = events[events.length - 1];
    let name = $derived(STATE.meet.name);

    function addEvent(eventData=lastEvent) {
        console.log("Adding event", eventData);
        let newEvent = new Event({
            ...eventData,
            n: (eventData?.n || 0) + 1,
        });
        addEventToMeet(newEvent);
        events = [...events, newEvent]; // Why is this necessary?
        console.log(STATE.meet.events, events);
    }
</script>

<div>
    {name}
    <div class = 'events'>
        {#each events as event (event.key)}
            <EventEditor {event} />
        {/each}
        <button class = 'sb tool new-event'
            onclick={() => addEvent()}>+ New Event
        </button>
    </div>
</div>

For some reason, I can only get the list of events to update reactively with the line events = [...events, newEvent]. But I thought the whole point of Svelte 5 deep reactivity was that I could just reassign STATE.meet.events and it would automatically update. For reference, here is addEventToMeet

export function addEventToMeet(newEvent) {
    STATE.meet.events = [...STATE.meet.events, newEvent];
}

I tried the above without reassigning events, and what happens is that both STATE.meet.events is updated, but events is not updated unless I reload the component. I was expecting events to update reactively since it is $derived from STATE.meet.events.

////////////////////// EDIT ////////////////////// I have isolated the issue to 2 files:

state.svelte.js

import Meet from "@src/shared/models/Meet";

export const TEST1 = $state({
  meet: new Meet({name: 'Test Meet', meetType: ''}),
});

export const TEST2 = $state({
  meet: {
    name: "test meet",
    type: {
      eventsTemplate: {
        events: [
          { n: 1,
            stroke: "Free",
            distance: 100
          }
        ]
      }
    }
  }
});

Test component:

<script>
    import { TEST1, TEST2 } from '@src/state/state.svelte.js';
    import Meet from '@shared/models/Meet';

    if (!TEST1.meet) TEST1.meet = new Meet({name: 'Test Meet 1', meetType: ''});

    console.log("TEST1", TEST1.meet);

    let events1 = $derived(TEST1.meet.type.eventsTemplate.events);
    let events2 = $derived(TEST2.meet.type.eventsTemplate.events);

    function addEvent() {
        const newEvent = {
            n: events2.length + 1,
            distance: 100,
            stroke: 'Freestyle'
        };
        TEST1.meet.type.eventsTemplate.events.push(newEvent);
        TEST2.meet.type.eventsTemplate.events.push(newEvent);
        console.log("Events after adding", TEST1.meet.type.eventsTemplate.events);
    }
</script>

<div>
    {TEST2.meet.name}
    <button class='sb tool new-event'
        onclick={() => addEvent()}>+ New Event
    </button>

    <h3>EVENTS 1</h3>
    <div class = 'events'>
        {#each events1 as event, index}
            <div>
                Event {event.n} - {event.distance} {event.stroke}
            </div>
        {/each}
    </div>
    <h3>EVENTS 2</h3>
    <div class = 'events'>
        {#each events2 as event, index}
            <div>
                Event {event.n} - {event.distance} {event.stroke}
            </div>
        {/each}
    </div>
</div>

What happens here is that EVENTS 2 is reactive and updates when I click the Add Event button, but to get EVENTS 1 to update, I have to re-render the component.

So it seems that creating a new Meet object and assigning it to the TEST1 state ruins reactivity. However, I would prefer for STATE.meet to be a full Meet object with all its methods rather than a basic JS object as in TEST2.

3
  • 1
    Show the definition of STATE, it's probably wrong. You also don't need to spread in Svelte 5 and can just push to the array, if $state is used correctly. The meet variable looks like a mistake as well, it creates a detached new state. Commented Sep 21 at 21:31
  • @brunnerh Thank you. Here is the definition of STATE: ``` export let STATE = $state({ meet: null, meetLoading: true, selectedEvent: null, view: null, viewOptions: [], user: null, isLoggedIn: false }); ``` Commented Sep 22 at 14:39
  • Add it to your question with all other relevant code that interacts with it, or better yet, create a proper minimal reproduction from scratch. The fact that STATE is a let declaration is also a red flag, if it is reassigned, all locations that import the object will hold the wrong reference. Commented Sep 22 at 18:39

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.