I'm trying to implement the onClick()
of a Material UI IconButton, that will decrement or increment the Calories value of each element in the table, like this. My code is based on the Table React ponent page code.
In this case, if I click in the [ - ] button, it will decrement the value to 2, and if I click in the [ + ] button, it will increment the value to 4.
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import IconButton from '@material-ui/core/IconButton';
//Icons
import AddCircleOutlineRoundedIcon from '@material-ui/icons/AddCircleOutlineRounded';
import RemoveCircleOutlineRoundedIcon from '@material-ui/icons/RemoveCircleOutlineRounded';
/*------------------------- IMPORTS ---------------------------*/
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
function createData(name, calories, fat, carbs, protein) {
return { name, calories, fat, carbs, protein };
}
const rows = [
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
createData('Eclair', 262, 16.0, 24, 6.0),
createData('Cupcake', 305, 3.7, 67, 4.3),
createData('Gingerbread', 356, 16.0, 49, 3.9),
];
export default function DenseTable() {
const classes = useStyles();
return (
<TableContainer ponent={Paper}>
<Table className={classes.table} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Dessert (100g serving)</TableCell>
<TableCell align="right">Calories</TableCell>
<TableCell align="right">Fat (g)</TableCell>
<TableCell align="right">Carbs (g)</TableCell>
<TableCell align="right">Protein (g)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map(row => (
<TableRow key={row.name}>
<TableCell ponent="th" scope="row">
{row.name}
</TableCell>
/* ----------------- IconButton HERE ---------------- */
<TableCell align="right">
<IconButton onClick={ --row.calories }>
<RemoveCircleOutlineRoundedIcon />
</IconButton>
{row.calories}
<IconButton onClick={ ++row.calories }>
<AddCircleOutlineRoundedIcon />
</IconButton>
</TableCell>
/* ----------------- IconButton END ---------------- */
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
In the 2 IconButtons
, I'm trying to decrement or increment Calories
value using onClick()
, but the way I'm doing this isn't working. What should I do? I think I need to use the ponent state
and therefore, the setState()
function, but I've no idea how to assign this and get that value.
I'm trying to implement the onClick()
of a Material UI IconButton, that will decrement or increment the Calories value of each element in the table, like this. My code is based on the Table React ponent page code.
In this case, if I click in the [ - ] button, it will decrement the value to 2, and if I click in the [ + ] button, it will increment the value to 4.
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import IconButton from '@material-ui/core/IconButton';
//Icons
import AddCircleOutlineRoundedIcon from '@material-ui/icons/AddCircleOutlineRounded';
import RemoveCircleOutlineRoundedIcon from '@material-ui/icons/RemoveCircleOutlineRounded';
/*------------------------- IMPORTS ---------------------------*/
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
function createData(name, calories, fat, carbs, protein) {
return { name, calories, fat, carbs, protein };
}
const rows = [
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
createData('Eclair', 262, 16.0, 24, 6.0),
createData('Cupcake', 305, 3.7, 67, 4.3),
createData('Gingerbread', 356, 16.0, 49, 3.9),
];
export default function DenseTable() {
const classes = useStyles();
return (
<TableContainer ponent={Paper}>
<Table className={classes.table} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Dessert (100g serving)</TableCell>
<TableCell align="right">Calories</TableCell>
<TableCell align="right">Fat (g)</TableCell>
<TableCell align="right">Carbs (g)</TableCell>
<TableCell align="right">Protein (g)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map(row => (
<TableRow key={row.name}>
<TableCell ponent="th" scope="row">
{row.name}
</TableCell>
/* ----------------- IconButton HERE ---------------- */
<TableCell align="right">
<IconButton onClick={ --row.calories }>
<RemoveCircleOutlineRoundedIcon />
</IconButton>
{row.calories}
<IconButton onClick={ ++row.calories }>
<AddCircleOutlineRoundedIcon />
</IconButton>
</TableCell>
/* ----------------- IconButton END ---------------- */
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
In the 2 IconButtons
, I'm trying to decrement or increment Calories
value using onClick()
, but the way I'm doing this isn't working. What should I do? I think I need to use the ponent state
and therefore, the setState()
function, but I've no idea how to assign this and get that value.
3 Answers
Reset to default 4 +50Yes, You must use setState
in onClick
. I'll tell you how to do it after I point out the mistakes you made.
There are few mistakes you're doing here,
Mistake 1: Not maintaining row
in a React State. All the dynamic data must be stored as a React State.
Mistake 2: onClick
isn't a function but a number. --row.Calorie
isn't a function, it's an expression which outputs number.
Mistake 3: Mutating the data directly. This is strictly prohibited in React and Functional Programming. You should not type expressions like --row.Calorie
. They directly mutate the data and React cannot track the changes.
Make these adjustments and you're good to go.
// Straight away create a row state like this.
const [rows, setRows] = useState([
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
createData('Eclair', 262, 16.0, 24, 6.0),
createData('Cupcake', 305, 3.7, 67, 4.3),
createData('Gingerbread', 356, 16.0, 49, 3.9),
])
// inside map function
rows.map((row, index) => (
// ...
<IconButton
// pass a function instead of expression
onClick={() => {
// NOTE: I'm using only index and prev in this block
// and not using row, or directly mutating them
setRows(prev => [
...prev.slice(0, index),
{ ...prev[index], calories: prev[index].calories - 1 },
...prev.slice(index + 1)
])
// Also NOTE: I'm creating a new state
// from the previous state - `prev` without
// mutating the `prev`
}}
>
<RemoveCircleOutlineRoundedIcon />
</IconButton>
// ...
))
The next code make the work.
You must to think in the next cycle:
when increment or decrement must to update the store value,
then in the row.map
show the store value.
It is the basic way to understand how react work.
In this case you use Hooks to set the state of your store,
I remend to you learn Redux.
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Table from '@material-ui/core/Table';
import TableBody from '@material-ui/core/TableBody';
import TableCell from '@material-ui/core/TableCell';
import TableContainer from '@material-ui/core/TableContainer';
import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow';
import Paper from '@material-ui/core/Paper';
import IconButton from '@material-ui/core/IconButton';
//Icons
import AddCircleOutlineRoundedIcon from '@material-ui/icons/AddCircleOutlineRounded';
import RemoveCircleOutlineRoundedIcon from '@material-ui/icons/RemoveCircleOutlineRounded';
/*------------------------- IMPORTS ---------------------------*/
const useStyles = makeStyles({
table: {
minWidth: 650,
},
});
function createData(name, calories, fat, carbs, protein) {
return { name, calories, fat, carbs, protein };
}
const rows = [
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
createData('Eclair', 262, 16.0, 24, 6.0),
createData('Cupcake', 305, 3.7, 67, 4.3),
createData('Gingerbread', 356, 16.0, 49, 3.9),
];
export default function DenseTable() {
const classes = useStyles();
const [calories, setCalories] = React.useState(rows);// set initial state is used only once
console.log(calories);
const onDecrement = key => () => {
setCalories( calories.map( (item, index) => item.name === key ?
{...item, calories: item.calories -1} : item));
};
const onIncrement = key => () => {
setCalories( calories.map( (item, index) => item.name === key ?
{...item, calories: item.calories +1} : item));
};
return (
<TableContainer ponent={Paper}>
<Table className={classes.table} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Dessert (100g serving)</TableCell>
<TableCell align="right">Calories</TableCell>
<TableCell align="right">Fat (g)</TableCell>
<TableCell align="right">Carbs (g)</TableCell>
<TableCell align="right">Protein (g)</TableCell>
</TableRow>
</TableHead>
<TableBody>
{calories.map(row => (
<TableRow key={row.name}>
<TableCell ponent="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">
<IconButton onClick={ onDecrement(row.name) }>
<RemoveCircleOutlineRoundedIcon />
</IconButton>
{row.calories}
<IconButton onClick={ onIncrement(row.name) }>
<AddCircleOutlineRoundedIcon />
</IconButton>
</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
React renders a ponent as a function of state and props, this makes the rendering deterministic (i.e for a given input the output will always be the same), this criteria allows react to know when to render a page. So whenever the state or props change the ponent is rendered again to reflect the change.
In your case onClick expects a function - an event handler. When user clicks your element, how should the element handle it? This needs to be defined by the user as a function.
<IconButton
onClick={() => {
--row.calories;
alert("Decremented to " + row.calories);
}}
>
<RemoveCircleOutlineRoundedIcon />
</IconButton>
{row.calories}
<IconButton
onClick={() => {
++row.calories;
alert("Incremented to " + row.calories);
}}
>
so we added a function and we can see that the value is reflected in the alert accurately. But the page doesn't seem to reflect the current value of calorie. This is because we never informed react that something was changed by user.
This could be done through state, we can choose to use state when we know some data can be altered by user and it needs to be rendered. State belongs to a ponent. State can be simple object with a single field or a plex one with multiple fields and objects.
let [foodData, setFoodData] = useState(rows);
/* existing code */
<TableBody>
{foodData.map((row, index) => (
/* existing code */
<IconButton
onClick={() => {
setFoodData(prev => [
...prev.slice(0, index),
{ ...prev[index], calories: prev[index].calories - 1 },
...prev.slice(index + 1)
]);
}}
>
/* do the same to increment */
The '...' is ES6 spread operator and to identify the current row which we want to modify we are using 'index' as a parameter for map function.
Now to modify the object we can create a new array that contains:
- All of the elements from 0 to index
- The object we want to modify
- All of the elements from index + 1 to the end of the array
Array’s slice method can achieve this as it does not modify the original array.
This new array will be set as the current state and react will re-render.
https://www.newline.co/fullstack-react/ - the first chapter of the book explains this in detail and introduces to state. useState is a hook which could be referred from here - https://reactjs/docs/hooks-state.html