munHunger
munHunger

Reputation: 2999

dom not updating when object is changed

I have what I believe is a quite small and simple svelte webapp, which is currently just a questionnaire. And I am trying to create a sort of multiple select system, where you can click a bunch of buttons and have them marked as selected (pretty much an html checkbox but it is slightly beside the point).

So in order to show to the user that an option is selected, I want to add a class to button if it is selected. However it is not being updated, and I can't really figure out why. The isSelected function is only called once per option and never when the setup object is modified, and I am wondering why that is

    <script>
      let page = 0;
      let setup = {
        JLPT: {
          question: "select JLPT levels",
          options: [1, 2, 3, 4, 5, 6, 7, 8, 9],
          selected: [],
          onSelect: option => {
            console.log(setup.JLPT.selected);
            setup.JLPT.selected = setup.JLPT.selected.concat(option);
          }
        }
      };
      $: question = getQuestion(page);
    
      function getQuestion(page) {
        return setup[Object.keys(setup)[page || 0]];
      }
    
      function isSelected(option) {
        return (question.selected || []).indexOf(option) > -1;
      }
    
      function onClick(option) {
        getQuestion(page).onSelect(option);
      }
    </script>
    
    
    <div class="wrapper">
      <div class="question">{question.question}</div>
      <div class="input">
        {#each question.options as option}
          <button
            on:click={() => onClick(option)}
            class={isSelected(option) ? 'selected' : ''}>
            {option}
          </button>
        {/each}
      </div>
    </div>

Upvotes: 3

Views: 2496

Answers (1)

Madacol
Madacol

Reputation: 4266

Replace $: question = getQuestion(page); with:

$: question = setup[Object.keys(setup)[page || 0]];

https://svelte.dev/repl/f7c2a4119829442a8e46ebb557bf1cf8?version=3.29.4

Why?

The way Reactive declarations work, is that svelte parses it and extracts the parameters it depends on. In the case of $: question = getQuestion(page); the dependencies are getQuestion and page, and whenever any of them changes, question will be recomputed.

But getQuestion and page never change, so question is never recomputed.

So what you want, is question to be recomputed whenever setup changes, and to do that you need to make sure question depends on setup.

In other words, you need the Reactive declarations to have this form

$: question = ...setup...

That principle also happens in this line

class={isSelected(option) ? 'selected' : ''}

Svelte parses it, and extracts as dependencies isSelected and option, and recomputes the "class attributes" whenever any of them changes.

But now you might think ... Hey! but they never change, so how is this even working?.

The reason is that option is part of the #each block that depends on question which in turns depends on setup.
So whenever setup changes (marked as "dirty") question is recomputed and marked as "dirty", and thus each "class attribute" is recomputed because indirectly they all depend on question.


BTW: a better solution to assign classes dynamically is using the class directive

class:selected={isSelected(option)}

You might want to read this post https://lihautan.com/compile-svelte-in-your-head-part-2/#reactive-declaration to get a better feeling of what Svelte is doing under the hood in Reactive declarations

Upvotes: 5

Related Questions