how to pass data upwards from child through an arbitrary amount of parents and changing it each time

79 Views Asked by At

How can I pass data from an innermost child component upwards through an arbitrary amount of different parent components, having each parent component modify the data all the way up until the outermost parent renders it?   For example, suppose I have some math utilities like initial(num: number), add(amount: number, num: number), and subtract(amount: number, num: number) that are used in vanilla js like:  

const total = add( 2, subtract( 4, initial(10) ) )
console.log(total) // logs: 8 ie: 10 - 4 + 2 

  How can I create components to use these in a declarative way, like:  

<Add amount={2}>
  <Subtract amount={4}>
    <Initial num={10} />
  </Subtract>
</Add>

  Which would output something like: <span>8</span>   I would like to be able to use an arbitrary amount of nesting between the different "modifier" components all nested around an initial "data setting" component. See REPL with the vanilla JS implementation: https://svelte.dev/repl/30ce69a25f1b46269e0a9918c84f36aa?version=4.2.0

I’ve tried using context and can’t quite get it to work.

2

There are 2 best solutions below

0
tsdexter On BEST ANSWER

Thanks to a very smart person on the Svelte Discord, I got a working and very elegant solution:

// App.svelte
<script>
    import Add from "./Add.svelte";
    import Subtract from "./Subtract.svelte";
    import Initial from "./Initial.svelte";
</script>

<Subtract amount={2}>
    <Add amount={4}>
      <Initial num={3} />
    </Add>
</Subtract>
// renders `5 <br />`
// Initial.svelte
<script>
    import { getContext } from "svelte";

    export let num;
    
    const context_fn = getContext("context_function") ?? ((e) => e);
</script>

{context_fn(num)} <br />
// Add.svelte
<script>
    import { getContext, setContext } from "svelte";

    export let amount;
    
    const context_fn = getContext("context_function") ?? ((e) => e);
    setContext("context_function", (arg) => amount + context_fn(arg))
</script>

<slot />
// Subtract.svelte
<script>
    import { getContext, setContext } from "svelte";

    export let amount;
    
    const context_fn = getContext("context_function") ?? ((e) => e);
    setContext("context_function", (arg) => context_fn(arg) - amount)
</script>

<slot />
2
José Ramírez On

This is such an interesting problem.

This REPL shows how to accomplish this using Svelte's data binding.

The idea is to expose the required input and calculated output as props, then databind the whole thing. This is not easy in any way, but does the job.

You'll notice this has "V2" in its name. My "V1" tried to make use of slot props. It works as well, but it is wordier and I even needed the tick() function to synchronize things.