I want to make a progress bar and I have two components. this is my chart component:
import Bar from "./Bar";
const INIT_MONTH_EXPENSES = [
{ label: "Jan", value: 0 },
{ label: "Feb", value: 0 },
{ label: "Mar", value: 0 },
{ label: "Apr", value: 0 },
{ label: "May", value: 0 },
{ label: "Jun", value: 0 },
{ label: "Jul", value: 0 },
{ label: "Aug", value: 0 },
{ label: "Sep", value: 0 },
{ label: "Oct", value: 0 },
{ label: "Nov", value: 0 },
{ label: "Dec", value: 0 },
];
const Chart = (props) => {
const totalExpenseAmountOfYear = props.items.reduce((total, item) => {
total = +total + +item.amount;
return total;
}, 0);
for (const expense of props.items) {
const expenseMonth = expense.date.getMonth();
INIT_MONTH_EXPENSES[expenseMonth].value += expense.amount;
}
//INIT_MONTH_EXPENSES[2].value give me the expected value but props.expenses[2].value in the Bar component give me a wrong value(actually doubled value)
return (
<div className="bg-pink-200 w-full h-[150px] p-4 mb-8 flex">
<Bar
expenses={INIT_MONTH_EXPENSES}
totalAmount={totalExpenseAmountOfYear}
/>
</div>
);
};
export default Chart;
the problem is that when i log passed props "expenses" in the other component it give me wrong values . any help will be appreciated.
When you pass an object declared in the parent component to the child component, in order for react to render the components again for the updated state you will have to use useState
or useReducer
hooks to notify the react engine that something has changed and I need you to re render the components again.
In the example you are just mutating the existing object and when you do that react has no idea that something has changed, so it ends up not rendering the child component with the updated data.
import Bar from "./Bar";
import { useState, useEffect } from 'react';
const INIT_MONTH_EXPENSES = [{
label: "Jan",
value: 0
},
{
label: "Feb",
value: 0
},
{
label: "Mar",
value: 0
},
{
label: "Apr",
value: 0
},
{
label: "May",
value: 0
},
{
label: "Jun",
value: 0
},
{
label: "Jul",
value: 0
},
{
label: "Aug",
value: 0
},
{
label: "Sep",
value: 0
},
{
label: "Oct",
value: 0
},
{
label: "Nov",
value: 0
},
{
label: "Dec",
value: 0
},
];
const Chart = (props) => {
// store the expenses in state so that it can be passed along.
// only run the initializer when the component is mounted
const [expenses, setExpenses] = useState(() => {
return [...INIT_MONTH_EXPENSES];
});
const [totalExpenses, setTotalExpenses] = useState(0);
const {
items
} = props;
useEffect(() => {
const updatedExpenses = items.reduce((memo, item) => {
const expenseMonth = item.date.getMonth();
if(memo[expenseMonth]) {
memo[expenseMonth].value += expense.amount;
}
return memo;
}, [...expenses]);
// this will notify react that state changed and will re render the component
setExpenses(updatedExpenses);
}, [items]);
// use effect to cal total expenses
useEffect(() => {
const totalExpenseAmountOfYear = items.reduce((total, item) => {
total = +total + (+item.amount);
return total;
}, 0);
setTotalExpenses(totalExpenseAmountOfYear);
}, [items]);
return ( <
div className = "bg-pink-200 w-full h-[150px] p-4 mb-8 flex" >
<
Bar expenses = {
expenses
}
totalAmount = {
totalExpenses
}
/> <
/div>
);
};
export default Chart;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Also need to make sure that setState
is not called directly in the render. It should typically be called in useEffect
, so you will have to figure out when the expenses need to be updated.