最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Material UI IconButton onClick to increment or decrement values - Stack Overflow

programmeradmin4浏览0评论

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&nbsp;(g)</TableCell>
            <TableCell align="right">Carbs&nbsp;(g)</TableCell>
            <TableCell align="right">Protein&nbsp;(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&nbsp;(g)</TableCell>
            <TableCell align="right">Carbs&nbsp;(g)</TableCell>
            <TableCell align="right">Protein&nbsp;(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.

Share Improve this question edited Mar 30, 2020 at 1:10 Grandtour asked Mar 25, 2020 at 15:14 GrandtourGrandtour 1,1971 gold badge11 silver badges28 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 4 +50

Yes, 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&nbsp;(g)</TableCell>
            <TableCell align="right">Carbs&nbsp;(g)</TableCell>
            <TableCell align="right">Protein&nbsp;(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

发布评论

评论列表(0)

  1. 暂无评论