In the following component (call it “annotation screen”), component DocPicker
let the user set the variable selectedDoc
, which in turn is supposed to update two components: Form
and SearchResultView
.
<script lang="ts">
import type { AppScreen, QuestionGouv } from '../../types.js';
import Screen from '../../Screen.svelte';
import DocPicker from './DocPicker/DocPicker.svelte';
import SearchResultView from '../../SearchResultView.svelte';
import Form from './Form.svelte';
export let screen: AppScreen;
let selectedDoc: QuestionGouv;
let isPickerOpen: boolean;
</script>
<Screen bind:screen>
<svelte:fragment slot="sidebar">
<DocPicker bind:picked={selectedDoc} bind:isOpen={isPickerOpen}/>
{#if selectedDoc}
<Form doc={selectedDoc}/>
{/if}
</svelte:fragment>
<svelte:fragment slot="content">
{#if selectedDoc}
<SearchResultView doc={selectedDoc} on:close={() => {selectedDoc = undefined}}/>
{/if}
</svelte:fragment>
</Screen>
The problem is that the Form
component is not updated when selectedDoc
changes. The first time selectedDoc
goes from undefined
to some value, component Form
appears with the proper value, but if later selectedDoc
changes (because the user used DocPicker
), component Form
stays with the previous value.
SearchResultView
is updated every time though, works like a charm, so I though the problem is coming from the Form
component instead, but if I copy line <Form doc={selectedDoc}/>
right next to where SearchResultView
is (that is, in the <svelte:fragment slot="content">
), this instance of Form
works without a problem! Similarly, if I put another instance of SearchResultView
in <svelte:fragment slot="sidebar">
, this instance of SearchResultView
does not update.
So it seems to be related to where the component is inserted, namely, in which slot of the Screen
component. But the Screen
component is pretty trivial, it boils down to:
<Sidebar>
<div class="screen-sidebar">
<slot name="sidebar"></slot>
</div>
</Sidebar>
<slot name="content"></slot>
And the Sidebar
component is trivial as well:
<div class="sidebar">
<slot></slot>
</div>
Okay, got it. It turned out that the cause of the problem was not in the code snippets that I put in my question. I did something you are probably not supposed to do at all in Svelte in the DocPicker
component. The Svelte compiler did not complain but got really confused apparently, and this resulted in the weird behavior I describe in the question.
Here are the details of the problem and how I found it.
I tried to come up with a minimal working example of the situation so that I could put it in a Svelte REPL, and of course the problem was not reproducing.
So I tried many intermediate steps from my problematic situation to the failed minimal working example, to see which precise step would brings back the bug. And what was bringing it back was the bind:isOpen={isPickerOpen}
prop binding in the DocPicker
component. So the problem is probably linked to this component.
I had a look in the code of this DocPicker
component, and prop isOpen
is used in a weird way. I had even left a comment warning about it:
export let isOpen: boolean = true;
$: isOpen = !Boolean(picked);
// XXX is it good practice to "force" a variable that is also reactively assigned?
function openPicker() {
isOpen = true;
}
So guess I now have my answer to the question in the comment: no, it's probably not a good idea. Or maybe not this way. If it's always a bad idea, couldn't the Svelte compiler raise an error when it sees this?
Anyway, I changed the way isOpen
is managed (not through reactive assignments, only imperative ones) and now it works like a charm.