Home > OS >  Need to calculate on the parent the result of a hook call on each subcomponent
Need to calculate on the parent the result of a hook call on each subcomponent

Time:01-23

I would love getting some help on this one, I think I am getting there, but I am not sure about it and need some guidance.

I have a parent component, which renders multiple subcomponents, and on each of those subcomponents, I get a result from a hook that do a lot of calculations and other multiple hook calls.

This hook only accepts and a single entity, not arrays, and I cannot afford to modify everything in order to accept arrays in them.

So let's say my parent component is

const Parent = () => {
const cardsArray = [...] 
return (
<Wrapper> 
        {cardsArray.map(
        card => <CardComponent cardId={cardId} />
        )}
</Wrapper>
)}

and my subComponent :

const CardComponent = ({cardId}) => {

 const result = useCalculation(cardId)
return (
        <div>My Calculation Result: {result}</div>
)}

Now my issue is this: I need to sum up all those results and show them in my Parent Component. What would be my best way to achieve this?

I thought about having an update function in my parent and pass it as a prop to my subcomponents, but, I am getting the problem that when the Card Subcomponent gets the result from the hook, calls the function and updates the parent state, although it works, I get an error on the console saying that I am performing a state update while rendering:

Cannot update a component (Parent) while rendering a different component (CardComponent). To locate the bad setState() call inside CardComponent, follow the stack trace as described in https://github.com/facebook/react/issues/18178#issuecomment-595846312

I feel like the answer must not be hard but I am not seeing it

thanks a lot

CodePudding user response:

I made some assumptions about your implementation but i think it will cover your needs.

Your thought about having an updater function on the parent element and pass it to it's children sounds pretty good and that's the basic idea of my proposed solution.

So let's start with the Parent component:

const Parent = () => {
  const cardsArray = [
    { cardId: 1 },
    { cardId: 2 },
    { cardId: 3 },
    { cardId: 4 }
  ];
  const [sum, setSum] = useState(0);

  const addToSum = useCallback(
    (result) => {
      setSum((prev) => prev   result);
    },
    [setSum]
  );

  return (
    <div>
      {cardsArray.map(({ cardId }) => (
        <CardComponent key={cardId} cardId={cardId} addToSum={addToSum} />
      ))}
      <strong>{sum}</strong>
    </div>
  );
};

I named your updater function addToSum assuming it aggregates and sums the results of the children elements. This function has 2 key characteristics.

  1. It's memoized with a useCallback hook, otherwise it would end up in an update loop since it would be a new object (function) on every render triggering children to update.
  2. It uses callback syntax for updating, in order to make sure it always uses the latest sum.

Then the code of your child CardComponent (along with a naive implementation of useCalculation) would be:

const useCalculation = (id) => {
  return { sum: id ** 10 };
};

const CardComponent = memo(({ cardId, addToSum }) => {
  const result = useCalculation(cardId);

  useEffect(() => {
    addToSum(result.sum);
  }, [result, addToSum]);

  return <div>My Calculation Result: {JSON.stringify(result)}</div>;
});

The key characteristics here are:

  1. the updater function run on an effect only when result changes (effect dependency).
  2. the addToSum dependency is there to make sure it will alway run the correct updater function
  3. it is a memoized component (using memo), since it has expensive calculations and you only want it to update when it's props change.

I assumed that useCalculation returns an object. If it returned a primitive value then things could be a little simpler but this code should work for every case.

You can find a working example in this codesandbox.

CodePudding user response:

I think you should use a shared state library (like Redux or React Context) to store all those calculations and access it in your parent (and sum them if you want).

CodePudding user response:

Create a state in the parent (sum in the example), and update it from the children in a useEffect block, which happens after rendering is completed:

const { useEffect, useState } = React

const useCalculation = cardId => cardId * 3

const CardComponent = ({ cardId, update }) => {
  const result = useCalculation(cardId)
  
  useEffect(() => {
    update(result)
  }, [result])
  
  return (
    <div>My Calculation Result: {result}</div>
  )
}

const Parent = ({ cardsArray }) => {
  const [sum, setSum] = useState(0);
  
  const updateSum = n => setSum(s => s   n)

  return (
    <div>
      {cardsArray.map(
        cardId => <CardComponent key={cardId} cardId={cardId} update={updateSum} />
      )}
      
      sum: {sum}
    </div>
  )
}

ReactDOM.render(
  <Parent cardsArray={[1, 2, 3]} />,
  root
)
<script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

<div id="root"></div>

  •  Tags:  
  • Related