0
votes

I have an app that simply hides content Hidden.svelte:

<script>
    export let shown = false;
</script>

<svelte:options accessors={true}/>

{#if shown}
    <slot/>
{/if}

Everything works fine in parent App.svelte:

<script>
    import Hidden from 'Hidden';

    let child;
</script>

<Hidden bind:this={child}>
    Content
</Hidden>

<button on:click={() => child.shown = true}>Show</button>

But, although I can do this on:click={child.shown = true}, I can not do this:

{#if child.shown}
    External message!
{/if}

Obviously, I also can not do this:

<div class:shown={child.shown}>Child component is shown</div>

I guess, thats all because it renders before mounting, but all my attempts playing with onMount and $: failed

Could it be achieved somehow? Thx

EDIT

Sorry, everyone, I've tried to make as simple example as possible, and made one that does not reflect my initial problem at all, but, however, technically got the right answer

So, the problem was, that parent App.svelte did not reflect child.shown changes that was made directly inside Hidden.svelte

@ThomasHennes suggested to use stores to solve that, but, if I got it right, it is good approach for single app instances, so, for those who are interested, I ended up with regular events:

https://svelte.dev/repl/f467fe36446444f09a2a7633b1faa6a1?version=3.20.1

EDIT 2

Real problem solved in accepted answer

2
Can you create a REPL? I am a bit flabbergasted by the child variable which is null at first, but then suddenly an object. svelte.dev/repl/hello-world?version=3.20.1Gh05d
@Gh05d svelte.dev/repl/c8667fd89c8442259233b0d3aa4b996b?version=3.20.1 Ye, I've previously missed export, now its fineMaxCore
So, the problem is solved?Gh05d
@Gh05d Sorry, things gone far, so, I've added "EDIT" block, and, for now, question closedMaxCore

2 Answers

5
votes

I think you may have overlooked a more direct possibility: bind:property.

While bind:this is very useful to grab DOM elements, I tend to consider it a last resort solution with components.

If you manage to make Svelte aware of what needs to be tracked, chances are that the resulting code will be more performant than if you roll your own with events or whatever. In part because it will use Svelte's runtime code that is already present in your app, in part because Svelte produces seriously optimized change tracking code, that would be hard to hand code all while keeping it human friendly. And in part because your change tracking targets will be more narrow.

Also, you'll get more concise code. More boilerplate left handling by the machine means more available human for the actual logic.

So... If you're interested in the changing value of a prop, you can bind to the prop. Here's how I would do it.

Hidden.svelte

<script>
  export let shown = false // <= you can bind to this!

  export const show = () => { shown = !shown } // <= you can bind to this, too!
</script>

<button on:click={show}>Child's button</button>

{#if shown}
  <slot/>
{/if}

App.svelte

<script>
  import Hidden from './Hidden.svelte'

  let shown
  let show
</script>

<button on:click={show}>Parent's button</button>

<Hidden bind:shown bind:show>
  <div>Hidden content</div>
</Hidden>

{#if shown}
  <div>Hidden child is shown!</div>
{/if}

REPL

1
votes

All you need to do is to test child along with child.shown:

{#if child && child.shown}
    External message!
{/if}

<div class:shown={!child || !child.shown}>Child component is shown</div>

See https://svelte.dev/repl/10f1e41e4fc3465d81bba5efcff84c4a?version=3.20.1

You could also use a reactive value to manage the combined conditions:

$: childShown = child && child.shown

and subsequently use childShown or !childShown to conditionnally display content.