user49011
user49011

Reputation: 533

How to pass data from child to parent with 2 Svelte components in HTML parent-child relationship

I'm new to Svelte. I have 2 Svelte components in an HTML parent-child relationship – as opposed to a Svelte P/C relationship (where 1 Svelte component imports another).

Ultimately, I want something like this (could have many Accs.):

  <Accordion header={--property from SvelteComponent-- }>
    <SvelteComponent />
  </Accordion>

I would like the SvelteComponent to define the header used in the Accordion (it can be dynamic). I can use stores for this but that seems too messy. Accordion does contain a slot but I can’t see how to pass information upward. Can anyone suggest a path forward?

Upvotes: 13

Views: 20147

Answers (3)

user49011
user49011

Reputation: 533

Svelte is great at getting rid of boilerplate and unnecessary extra code. I want to take that as far as I can.

I believe using @skeletizzle answer that I would have to add a variable to the container of the Accordions, for each Accordion and its child. THAT is what I want to eliminate (it pollutes the namespace of the container for a trivial operation). Since the Accordion and its child are in a P/C relation - they know about each other and the compiler could make communication direct. Think of an implied interface where the Accordion looks in its child and it it finds a prop named, say 'header', automatically, reactively, uses that for its own header prop.

What I am going to do is use stores though as stated, I feel they are too heavy - but don't think I have an alternative.

WrappedComponent.svelte

<script context="module">
  import { writable } from 'svelte/store'
  export let thisComponentHeader = writable('default header')
</script>

App.svelte

import WrappedComponent, { thisComponentHeader } from './WrappedComponent.svelte'
...
<Accord header={$thisComponentHeader}>
  <WrappedComponent />
</Accord>

There still is a variable. but it is socked away in the import definition

One thing this prevents is having more than 1 instance of the component.

Upvotes: 2

skeletizzle
skeletizzle

Reputation: 251

One option is to use component bindings. This allows a two-way binding between a value in the scope of a container component and a prop of the contained component.

<Accordion {header}>
    <SvelteComponent bind:header={header} />
</Accordion>

and in SvelteComponent.svelte:

<script>
    export let header = "defaultHeader";
</script>

Whenever SvelteComponent makes a change to defaultHeader, it will propogate back up via the binding to the file which contains the Accordion and SvelteComponent, and apply the changes back downward. https://svelte.dev/tutorial/component-bindings

Alternatively, you can provide a setHeader function as a prop to SvelteComponent, which sets the value of header:

//SvelteComponent.svelte
<script>
    export let setHeader;
</script>

<div on:click={() => setHeader("myHeader")}>
    My Svelte Component
</div>

Upvotes: 25

JakubJ
JakubJ

Reputation: 41

What about store and context management? Instead of creating module level store, set it in the component context. https://svelte.dev/docs#run-time-svelte-setcontext

Accordion.svelte

<script>
  import {writable} from "svelte/store";
  import {setContext} from "svelte";
  const header = writable('Default header');
  setContext('header', header)
</script>
<div>{$header}</div>

WrappedComponent.svelte

<script>
  import {getContext} from 'svelte';
  const header = getContext('header');
  if(header) $header = 'New fancy header'
</script>

App.svelte

<Accordion>
  <WrappedComponent />
</Accordion>

Upvotes: 3

Related Questions