I'm trying to create an expenses track app using react JS. The problem is that I'm updating the state of the variable, but the UI doesn't change accordingly. For each new expense added, the idea is that:
groupedExpenses
is updated, but the UI of the expense line isn't updated.Below is my code:
import Expenses from "./components/Expenses";
import Form from "./components/Form";
import { useState } from "react";
function App() {
const [groupedExpenses, setGroupedExpenses] = useState({});
function addExpense(name, amount) {
setGroupedExpenses((previousGroupedExpenses) => {
let newGroupedExpenses = { ...previousGroupedExpenses };
console.log(newGroupedExpenses);
if (name in newGroupedExpenses) {
newGroupedExpenses[name] += parseFloat(amount.replace(",", "."));
} else {
newGroupedExpenses[name] = parseFloat(amount.replace(",", "."));
}
return newGroupedExpenses;
});
}
return (
<div className="expensesapp stack-large">
<h1>Expense Manager</h1>
<Form addExpense={addExpense} />
<Expenses groupedExpenses={groupedExpenses} />
</div>
);
}
export default App;
import { useState } from "react";
import TextField from "@mui/material/TextField";
import Stack from "@mui/material/Stack";
import Autocomplete from "@mui/material/Autocomplete";
import InputAdornment from "@mui/material/InputAdornment";
import Icon from "@mui/material/Icon";
function Form(props) {
const [expenseName, setExpenseName] = useState("");
const [expenseAmount, setExpenseAmount] = useState("");
const [options, setOptions] = useState([]);
function handleChangeName(event) {
setExpenseName(event.target.value);
}
function handleSelectSuggestion(event, value) {
setExpenseName(value);
}
function handleChangeAmount(event) {
setExpenseAmount(event.target.value);
}
function handleSubmit(event) {
event.preventDefault();
props.addExpense(expenseName, expenseAmount);
if (!options.includes(expenseName)) {
setOptions([...options, expenseName]);
}
setExpenseName("");
setExpenseAmount("");
}
return (
<form onSubmit={handleSubmit}>
<div className="expense">
<Stack direction="row" spacing={1} sx={{ width: 300 }}>
<Autocomplete
sx={{ m: 1, width: "25ch" }}
id="free-solo"
freeSolo
onInputChange={handleChangeName}
onChange={handleSelectSuggestion}
options={options}
renderInput={(params) => (
<TextField {...params} label="nome" value={expenseName} />
)}
/>
<TextField
label="valor"
id="outlined-start-adornment"
sx={{ m: 1, width: "25ch" }}
onChange={handleChangeAmount}
value={expenseAmount}
slotProps={{
input: {
startAdornment: (
<InputAdornment position="start">R$</InputAdornment>
),
},
}}
/>
<button type="submit" className="btn btn__primary btn__lg">
<Icon>add_circle</Icon>
</button>
</Stack>
</div>
</form>
);
}
export default Form;
function Expenses(props) {
console.log(props.groupedExpenses);
return (
<ul
role="list"
className="expenses-list stack-large stack-exception"
aria-labelledby="list-heading"
>
{props.groupedExpenses !== undefined &&
Object.entries(props.groupedExpenses).map(([k, v]) => (
<div>
<li className="expense stack-small">
<input
type="text"
className="input input-name input__lg"
name="name"
autoComplete="off"
defaultValue={k}
readOnly
/>
<input
type="text"
className="input input-value input__lg"
name="value"
autoComplete="off"
defaultValue={v}
readOnly
/>
</li>
</div>
))}
</ul>
);
}
export default Expenses;
Could anyone point out what am I doing wrong? Thanks!
The issue that you're facing is because of defaultValue
in Expenses.jsx
defaultValue
is used to set an initial value. If the state changes, the defaultValue
won't trigger the re-render. That's the reason it's not reflecting in the UI. Since it's not re-rendered, it's not updating in the UI
Solution
You can use value
instead of defaultValue
. This should resolve the issue.