skip to Main Content

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.

3

Answers


  1. Chosen as 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. 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.

    Login or Signup to reply.
  3. To achieve the behavior you described, where data flows from an innermost child component upwards through multiple parent components, you can use React’s context API and React’s component composition. You can create a context that provides the necessary functions for performing mathematical operations and wrap your components in this context. Here’s a step-by-step guide on how to do this:

    Create a Math Context:

    1: First, create a context that will provide the math utility functions (initial, add, subtract).

    // MathContext.js
    import React, { createContext, useContext } from 'react';
    
    const MathContext = createContext();
    
    export function MathProvider({ children }) {
      const initial = (num) => num;
      const add = (amount, num) => num + amount;
      const subtract = (amount, num) => num - amount;
    
      return (
        <MathContext.Provider value={{ initial, add, subtract }}>
          {children}
        </MathContext.Provider>
      );
    }
    
    export function useMath() {
      return useContext(MathContext);
    }
    

    2: Create Your Components:

    Create components for Initial, Add, and Subtract that use the useMath hook to access the math utility functions.

    // Initial.js
    import React from 'react';
    import { useMath } from './MathContext';
    
    function Initial({ num }) {
      const { initial } = useMath();
      return initial(num);
    }
    
    export default Initial;
    

    Then

    // Add.js
    import React from 'react';
    import { useMath } from './MathContext';
    
    function Add({ amount, children }) {
      const { add } = useMath();
      return children(add(amount));
    }
    
    export default Add;
    

    Then:

    // Subtract.js
    import React from 'react';
    import { useMath } from './MathContext';
    
    function Subtract({ amount, children }) {
      const { subtract } = useMath();
      return children(subtract(amount));
    }
    
    export default Subtract;
    

    3: Use Your Components:

    Now, you can use these components in a declarative way, just like in your example.

    // App.js
    import React from 'react';
    import { MathProvider } from './MathContext';
    import Add from './Add';
    import Subtract from './Subtract';
    import Initial from './Initial';
    
    function App() {
      return (
        <MathProvider>
          <Add amount={2}>
            {(addResult) => (
              <Subtract amount={4}>
                {(subtractResult) => (
                  <Initial num={10}>
                    {(initialResult) => (
                      <div>
                        Total: {addResult + subtractResult + initialResult}
                      </div>
                    )}
                  </Initial>
                )}
              </Subtract>
            )}
          </Add>
        </MathProvider>
      );
    }
    
    export default App;
    

    Now, when you render App, it will calculate the total by composing the Add, Subtract, and Initial components, and the result will be displayed in the div. Data flows from the innermost child component upwards through the parents, and each parent modifies the data as needed until the outermost parent renders it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search