Still learning React so please be understanding.
In a component I have declared a list of elements. From a parent component when new element is added I pass that element via prop. The problem is that I'd like to update a list only when new element pops in. I have code like that:
export default function Expenses(props) {
const [expenses, setExpenses] = useState(INITIAL_EXPENSES);
const [filteredYear, setFilteredYear] = useState('2020');
const filterExpenses = (date) => {
setFilteredYear(date);
}
if (props.newExpense.hasOwnProperty('expenseTitle')) {
setExpenses((prev) => {
const newExpenses = [...prev]
newExpenses.push(props.newExpense);
props.newExpense = null;
return newExpenses;
})
}
const filteredExpenses = expenses.filter(exp => exp.expenseDate.getFullYear().toString() === filteredYear);
return (
<Card className="expenses">
<ExpensesFilter year={filteredYear} onExpensesFilter={filterExpenses}/>
<ExpenseChart expenses={filteredExpenses}/>
<ExpensesList items={filteredExpenses}/>
</Card>
)
}
I suppose that if
if (props.newExpense.hasOwnProperty('expenseTitle')) {
is not well done because somehow props.newExpense should be cleared after added to newExpenses list. But I don't know how to do it?
The second trouble which I have is an error Too many re-renders. React limits the number of renders to prevent an infinite loop. which in that form of code is completly fine cause after adding new element to list inside props.newExpense there is still that element so that if again and again is true.
Please do not change the way how I declared Expenses class/function. That's not the point of this issue.
So the problem with your code is basically you're setting the state on every single render, and when you set the state to a new value it triggers another render, which in turn sets the state and...you get the gist.
Enter the useEffect hook. useEffect allows you to define side effects to changes in particular state or props.
What you want to do here is do something only when the newExpense prop changes, so you'd have to do something like that: (Note you need to import useEffect from react)
useEffect(() => {
if (props.newExpense.hasOwnProperty('expenseTitle')) {
setExpenses((prev) => {
const newExpenses = [...prev]
newExpenses.push(props.newExpense);
props.newExpense = null;
return newExpenses;
})
}, [props.newExpense])
The 2nd argument to useEffect is a dependency array, meaning if one of the things in the array changes, run the effect function.
Do note that it's seldom a good thing to set the state in useEffect, so I do suggest refactoring the mechanism if you can