javascriptcomponentssveltesveltekitdeclarative

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


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.


Solution

  • 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 />