skip to Main Content

I want to have 2 functional React components:

  • a Data component that holds a value in state, that is passed through props:

  • a SummedValue that also holds a value in state, but calculates this value as the sum of it’s children’s value, where its children can be either Data or other SummedValue components, nested arbitrarily
    I would like to use them like this:

     <SummedValue>               
         <Data value={3}/>
         <Data value={5}/>
     <SummedValue>           
         <Data value={4}/>
         <Data value={1}/>
     </SummedValue>
    

Currently, in the SummedValue component, I can get the data from Data components alone by children.props.value. However I dont know how to get this value from nested SummedValue components, as it is not a prop.

Basically I think this is related to passing data from the child to the parent, but I cant find the configuration that works

function SummedValue({children}) {
// how can I access this computed value in parent component?
let computedValue = 0;

// Only works for props, not internal values
children.forEach(child => {
    if (child.props.value) {
        value += child.props.value
    }
})

return(
    <div>
        <span>SUMMED NUMBER IS: {computedValue}
        {children}
    </div>
)

}

4

Answers


    • You need to recursively compute the sum from both Data & SummedValue components.

    • The Data component holds value in its state via props.

    • The SummedValue component needs to iterate over its children & compute the sum of Data components directly & recursively handles the nested SummedValue components.

    • SummedValueContext is created to pass the summed value up the tree.

    • useContext(SummedValueContext) is used to get the parent’s summed value updater function.

    • The SummedValueContext.Provider is used to provide the local summed value updater function to its children.

    • Here is the impelement code for above functionality:

    import React, { createContext, useContext, useState, useEffect } from 'react';
    
    const SummedValueContext = createContext();
    
    function Data({ value }) {
      return <div>{value}</div>;
    }
    
    function SummedValue({ children }) {
      const parentSummedValue = useContext(SummedValueContext);
      const [computedValue, setComputedValue] = useState(0);
    
      useEffect(() => {
        let sum = 0;
    
        const sumValues = (children) => {
          children.forEach(child => {
            if (child.props.value !== undefined) {
              sum += child.props.value;
            } else if (child.type === SummedValue) {
              sum += child.props.children.reduce((acc, child) => {
                if (child.props.value !== undefined) {
                  return acc + child.props.value;
                } else if (child.type === SummedValue) {
                  return acc + sumValues(child.props.children);
                }
                return acc;
              }, 0);
            }
          });
        };
    
        sumValues(children);
        setComputedValue(sum);
    
        if (parentSummedValue) {
          parentSummedValue(sum);
        }
      }, [children, parentSummedValue]);
    
      return (
        <SummedValueContext.Provider value={setComputedValue}>
          <div>
            <span>SUMMED NUMBER IS: {computedValue}</span>
            {children}
          </div>
        </SummedValueContext.Provider>
      );
    }
    
    function App() {
      return (
        <SummedValue>
          <Data value={3} />
          <Data value={5} />
          <SummedValue>
            <Data value={4} />
            <Data value={1} />
          </SummedValue>
        </SummedValue>
      );
    }
    
    export default App;
    
    Login or Signup to reply.
  1. As we know Context passes through intermediate components while Using and providing context from the same component. The sample code below follows the same principle, please see the output below prior to proceeding with the code.

    Note : This is a naive code, please see the another answer, the code in it is handling the known edge cases as well.

    Browser display of the output

    Please take advantage of the comments enclosed in the code.

    App.js

    import { useContext } from 'react';
    import { RunningSectionSumContext } from './context';
    
    export default function App() {
      return (
        <>
          <SummedValue>
            <Data value={10} />
            <Data value={5} />
            <SummedValue>
              <Data value={20} />
              <Data value={30} />
            </SummedValue>
          </SummedValue>
        </>
      );
    }
    
    function SummedValue({ children }) {
      // Destructuring nested objects.
      const sectionSum = children
        // filter the children with value, the Data components only
        .filter(({ props: { value } }) => value) 
        .map(({ props: { value } }) => value)
        .reduce((a, b) => a + b, 0); // find the section sum
    
      // the statement using the context.
      const runningSectionSum = useContext(RunningSectionSumContext) + sectionSum;
      return (
        <div
          style={{
            border: '2px dotted white',
            margin: 10,
            padding: 10,
            width: '50%',
          }}
        >
          <strong>Running section sum is : {runningSectionSum}</strong>
          <!-- the statement providing the new value to the context -->
          <RunningSectionSumContext.Provider value={runningSectionSum}>
            <!-- while evaluating the children below, the same component
             will be invoked recursively if the child component is
             is a nested SummedValue component. -->
            {children}
          </RunningSectionSumContext.Provider>
          <br />
        </div>
      );
    }
    
    function Data({ value }) {
      return (
        <>
          <br />
          <em>data value : {value}</em>
        </>
      );
    }
    
    Login or Signup to reply.
  2. Optimised implementation:

    For a naive implementation, please see the another answer.

    Note : Please take advantage of the comments enclosed in the code.

    Output:

    Browser display

    App.js

    import { useContext } from 'react';
    import { RunningSectionSumContext } from './context';
    
    export default function App() {
      return (
        <>
          <SummedValue>
            <Data value={10} />
            <Data value={5} />
            <SummedValue>
              <Data value={20} />
              <Data value={30} />
            </SummedValue>
            <SummedValue>
              <Data value={20} />
            </SummedValue>
            <SummedValue />
          </SummedValue>
          <SummedValue />
        </>
      );
    }
    
    function SummedValue({ children }) {
      // Destructuring nested objects.
      let sectionSum;
    
      // case 1: the children prop will be an array
      // when the component has more than one nested component
      if (Array.isArray(children)) {
        sectionSum = children
          .filter(({ props: { value } }) => value)
          .map(({ props: { value } }) => value)
          .reduce((a, b) => a + b, 0);
      } else if (children) {
        // case 2: the children prop will be an object
        // when the component has only one nested component
        // this is the case of the below jsx
        // <SummedValue>
        //   <Data value={20} />
        // </SummedValue>
    
        sectionSum = children.props.value;
      } else {
        sectionSum = 0;
        // case 3 : The children is neither an array nor an object,
        // it means there is no nested element. This would occur
        // during the recursive calls of the component.
        // Or it can happen for the below jsx as well.
        // <SummedValue /> // no children  enclosed.
      }
    
      const runningSectionSum = useContext(RunningSectionSumContext) + sectionSum;
      return (
        <div
          style={{
            border: '2px dotted white',
            margin: 10,
            padding: 10,
            width: '50%',
          }}
        >
          <strong>Running section sum is : {runningSectionSum}</strong>
          <RunningSectionSumContext.Provider value={runningSectionSum}>
            {children}
          </RunningSectionSumContext.Provider>
          <br />
        </div>
      );
    }
    
    function Data({ value }) {
      return (
        <>
          <br />
          <em>data value : {value}</em>
        </>
      );
    }
    
    Login or Signup to reply.
  3. To solve this problem,

    1. Pass a ref from the parent SummedValue component to its child SummedValue components. This allows us to access the computed value from the child components when they are nested. By using React’s useRef(), we can store and reference the computed values within SummedValue.

    2. Explicitly pass the summedValueRef as a prop to each SummedValue child. This allows the parent SummedValue to access the calculated value of any nested SummedValue component and include it in its own total sum.

    import React, { useState, useEffect, useRef } from 'react';
    
    function Data({ value }) {
      const [dataValue, setDataValue] = useState(value);
    
      return (
        <div>
          Data Value: {dataValue}
        </div>
      );
    }
    
    function SummedValue({ children, summedValueRef }) {
      const [summedValue, setSummedValue] = useState(0);
      const localSummedValueRef = useRef(0);
    
      useEffect(() => {
        let total = 0;
    
        // Iterate through children
        React.Children.forEach(children, (child) => {
          // If child is a `Data` component, extract its value
          if (child.type === Data) {
            total += child.props.value;
          }
          // If child is a `SummedValue` component, use the ref to access the calculated value
          else if (child.type === SummedValue) {
            total += child.props.summedValueRef.current;
          }
        });
    
        // Store the computed total in the ref
        localSummedValueRef.current = total;
        setSummedValue(total);
        
        // Pass the computed value to parent `SummedValue`, if ref is provided
        if (summedValueRef) {
          summedValueRef.current = total;
        }
      }, [children]);
    
      return (
        <div>
          Summed Value: {summedValue}
          {children}
        </div>
      );
    }
    
    export function App() {
      return (
        <SummedValue summedValueRef={useRef(0)}>
          <Data value={5} />
          <Data value={5} />
          <SummedValue summedValueRef={useRef(0)}>
            <Data value={6} />
            <Data value={1} />
          </SummedValue>
        </SummedValue>
      );
    }
    

    Summed value

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