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
};