ClassY
ClassY

Reputation: 655

Updating state inside querySelector not working in svelte

I have a svelte component like this:

<script lang="ts">
    import { faBarsStaggered } from '@fortawesome/free-solid-svg-icons';
    import { onMount } from 'svelte';
    import Fa from 'svelte-fa/src/fa.svelte';

    let navbarItemsWrapper: HTMLUListElement;
    let showDrawer = false;

    onMount(() => {
        navbarItemsWrapper.querySelectorAll('a').forEach((element) => {
            element.addEventListener('click', () => {
                showDrawer = false;
            });
        });
    });
</script>

<div class="drawer">
    <input id="navbar-drawer" type="checkbox" class="drawer-toggle" bind:checked={showDrawer} />
    <div class="drawer-content flex flex-col">
        <!-- Navbar -->
        <div class="w-full navbar">
            <div class="flex-none lg:hidden">
                <label for="navbar-drawer" class="btn btn-square btn-ghost">
                    <Fa icon={faBarsStaggered} />
                </label>
            </div>
        </div>
    </div>
    <div class="drawer-side z-10">
        <label for="navbar-drawer" class="drawer-overlay" />
        <ul class="menu p-4 w-80 h-full bg-base-200" bind:this={navbarItemsWrapper}>
            <slot name="drawer-items" />
        </ul>
    </div>
</div>

As you can see, I'm updating the showDrawer variable when a user clicks on a link. But this wont update the checkbox checked state. But if I bind the checked attribute of input to showDrawer instead like this:

<input id="navbar-drawer" type="checkbox" class="drawer-toggle" bind:checked={showDrawer} />

everything works like a charm. Using bind:[name]={variable} is for two-way data binding which is not something that I need here. So why do I need have to use it for this to work?

---- EDIT: To get more infomation about how svelte reactivity system works let me add more questions about this topic:

  1. What would be the difference if I used the svelte's on:click. Because if that was the case the reactivity system would understand that it needs to update the showDrawer on the checkbox
  2. Why svelte doesn't understand that I've updated the showDrawer variable?

Upvotes: 0

Views: 194

Answers (1)

brunnerh
brunnerh

Reputation: 185180

This should just work. As soon as any local state is modified, it will be invalidated if used reactively (e.g. in markup). Hard to know what the real issue is without a minimal, completely contained example.

As for what happens when using on:click: It has no bearing on reactivity at all.
The primary difference is, that it is just cleaner:

  • It is declarative and the idiomatic way of doing things
  • Cleans up event handlers automatically, when the component is destroyed

The approach you have taken here is really not good. You claim that "the parent should not care how the drawer is closed" yet the parent has to know that a elements need to be passed in, for any of this to work. And the drawer attaches events to elements it does not own.

There are various better ways of doing this. E.g.

  • Use on:click on an ancestor of the <slot> and just let the event bubble up.
    You could inspect the event in the handler to limit the logic to relevant elements if some need to be ignored. (In that case there still is an obscure contract going on.)
  • Use coupled components via context, e.g. set a drawer context object that exposes a function to close the drawer and have a separate component for navigation items. The navigation item component can check whether there is a drawer context and in that case close the drawer on click.
  • Provide a way to close the drawer to the user of the drawer. Your claim that "the parent should not care how the drawer is closed. It is something internal to the drawer itself" seems just wrong to me, anyway. Depending on what one adds to a drawer, one may want to close it upon interaction or not.
    This can easily be done via slot props, e.g.
    <Drawer let:close>
        <a href="..." on:click|preventDefault={close}>Item</a>
    </Drawer>
    
    <slot close={() => showDrawer = false} />
    

Upvotes: 1

Related Questions