React component does not rerender after useEffect (dependency array used)

I have an object state priceRanges in component A , which i initialize with a key and a value:

const [priceRanges, setPriceRanges] = useState({
        [randomId]: {
            "price": 0,
            "quantity": 0
        }
    })

And then pass it through props to component B:

<ComponentB pricesRanges={pricesRanges} ...props />

The problem is that in Component B , I am converting this object into an array on useEffect:

React.useEffect(() => {
        const getArrayData = () => {
            const keys = Object.keys(priceRanges)
            keys.map(key => {
                console.log(priceRanges[key]["price"])
                priceRangesArray.push({
                    id: key,
                    price: priceRanges[key]["price"] ? priceRanges[key]["price"] : 0,
                    quantity: priceRanges[key]["quantity"] ? priceRanges[key]["quantity"] : 0
                })
            })
        }
        getArrayData()
    }, [priceRanges])

But the component does not rerender after the useEffect has run. As such, the component does not render the initial state that pricesRanges was initialized with. To test this, I did this:

{priceRangesArray.length > 0 ? 
            (priceRangesArray.map(quantityAndPrice => {
                return (
                    <div className="flex" key={quantityAndPrice.id}>
                        {console.log("I AM RENDERING")}
                        <input
                            ...props
                        />
                        <input
                            ...props
                        />
                    </div>
                )
            })) :<p>Hello</p>}

Which always renders Hello on mount . How can I tackle this ? Thanks you !


Solution 1:

The only way to cause a rerender is to set state. Mutating an array during a useEffect will not cause a rerender. So you could fix this by adding a state variable, and then setting state once you've calculated the array.

However, i think that's not the right approach here. Your array is derived data: it's directly based on what's in priceRanges, and you don't want it to ever deviate from that. So for this, i would calculate the value in line during rendering the component:

const Example = ({ priceRanges }) => {
  const keys = Object.keys(priceRanges);
  const priceRangesArray = keys.map((key) => {
    return {
      id: key,
      price: priceRanges[key]["price"] ? priceRanges[key]["price"] : 0,
      quantity: priceRanges[key]["quantity"] ? priceRanges[key]["quantity"] : 0,
    };
  });

  // ...rest of the component
}

If performance is a concern, you can wrap this in useMemo to skip the calculation if priceRanges has not changed.

const Example = ({ priceRanges }) => {
  const priceRangesArray = useMemo(() => {
    const keys = Object.keys(priceRanges);
    return keys.map((key) => {
      return {
        id: key,
        price: priceRanges[key]["price"] ? priceRanges[key]["price"] : 0,
        quantity: priceRanges[key]["quantity"] ? priceRanges[key]["quantity"] : 0,
      };
    });
  }, [priceRanges]);

  // ... rest of the component
};