3

I am having a table which will contain list of students fetched from a database. When I click each student row a modal popup will be shown containing another table of 4 rows and 3 columns. Each column in the modal will contain 4 checkboxes. When I click a checkbox I want to change the status column of that row from 'pending' to 'paid' and also the the badge property type from 'danger' to 'success'. The problem is when I click a checkbox it changes all the columns status from 'pending' to 'paid' instead of just changing that single single row.

Here is my code

import {
  Table,
  TableBody,
  TableCell,
  Badge,
  TableFooter,
  TableRow,
  TableHeader,
  TableContainer,
  Input,
  Modal,
  ModalHeader,
  ModalBody,
  ModalFooter,
  Button,
} from "@windmill/react-ui";
import MaterialTable from "material-table";
import AddBox from "@material-ui/icons/AddBox";
import ArrowDownward from "@material-ui/icons/ArrowDownward";
import Check from "@material-ui/icons/Check";
import ChevronLeft from "@material-ui/icons/ChevronLeft";
import ChevronRight from "@material-ui/icons/ChevronRight";
import Clear from "@material-ui/icons/Clear";
import DeleteOutline from "@material-ui/icons/DeleteOutline";
import Edit from "@material-ui/icons/Edit";
import FilterList from "@material-ui/icons/FilterList";
import FirstPage from "@material-ui/icons/FirstPage";
import LastPage from "@material-ui/icons/LastPage";
import Remove from "@material-ui/icons/Remove";
import SaveAlt from "@material-ui/icons/SaveAlt";

import Search from "@material-ui/icons/Search";
import ViewColumn from "@material-ui/icons/ViewColumn";
import React, { forwardRef, useState } from "react";
const Admin = () => {
  const [isModalOpen, setIsModalOpen] = useState(false);
  const [id, setId] = useState("");

  function openModal(ids) {
    setId(ids.studentId);
    setIsModalOpen(true);
  }
  function closeModal() {
    setIsModalOpen(false);
  }

  const tableIcons = {
    Add: forwardRef((props, ref) => <AddBox {...props} ref={ref} />),
    Check: forwardRef((props, ref) => <Check {...props} ref={ref} />),
    Clear: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
    Delete: forwardRef((props, ref) => <DeleteOutline {...props} ref={ref} />),
    DetailPanel: forwardRef((props, ref) => (
      <ChevronRight {...props} ref={ref} />
    )),
    Edit: forwardRef((props, ref) => <Edit {...props} ref={ref} />),
    Export: forwardRef((props, ref) => <SaveAlt {...props} ref={ref} />),
    Filter: forwardRef((props, ref) => <FilterList {...props} ref={ref} />),
    FirstPage: forwardRef((props, ref) => <FirstPage {...props} ref={ref} />),
    LastPage: forwardRef((props, ref) => <LastPage {...props} ref={ref} />),
    NextPage: forwardRef((props, ref) => <ChevronRight {...props} ref={ref} />),
    PreviousPage: forwardRef((props, ref) => (
      <ChevronLeft {...props} ref={ref} />
    )),
    ResetSearch: forwardRef((props, ref) => <Clear {...props} ref={ref} />),
    Search: forwardRef((props, ref) => <Search {...props} ref={ref} />),
    SortArrow: forwardRef((props, ref) => (
      <ArrowDownward {...props} ref={ref} />
    )),
    ThirdStateCheck: forwardRef((props, ref) => (
      <Remove {...props} ref={ref} />
    )),
    ViewColumn: forwardRef((props, ref) => <ViewColumn {...props} ref={ref} />),
  };
  const [status, setStatus] = useState("pending");
  const [badge, setBadge] = useState("danger");

  const handleClick = (e) => {
    //Do something if checkbox is clicked
    if (e.target.checked) {
      setStatus("paid");
      setBadge("success");
    } else {
      setStatus("pending");
      setBadge("danger");
    }
  };

  // Table to be rendered in the modal
  function renderTable() {
    return (
      <TableContainer>
        <Table>
          <TableHeader>
            <TableRow>
              <TableCell>Dues</TableCell>
              <TableCell>Amount</TableCell>
              <TableCell>Status</TableCell>
            </TableRow>
          </TableHeader>
          <TableBody>
            <TableRow>
              <TableCell>
                <div className='flex items-center text-sm'>
                  <span className='font-semibold ml-2'>Level 100</span>
                </div>
              </TableCell>
              <TableCell>
                <span className='text-sm'>100</span>
              </TableCell>
              <TableCell>
                <Badge type={badge}>{status}</Badge>
                <Input
                  type='checkbox'
                  className='ml-6'
                  onClick={(e) => handleClick(e)}
                />
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <div className='flex items-center text-sm'>
                  <span className='font-semibold ml-2'>Level 200</span>
                </div>
              </TableCell>
              <TableCell>
                <span className='text-sm'>100</span>
              </TableCell>
              <TableCell>
                <Badge type={badge}>{status}</Badge>
                <Input
                  type='checkbox'
                  className='ml-6'
                  onClick={(e) => handleClick(e)}
                />
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <div className='flex items-center text-sm'>
                  <span className='font-semibold ml-2'>Level 300</span>
                </div>
              </TableCell>
              <TableCell>
                <span className='text-sm'>100</span>
              </TableCell>
              <TableCell>
                <Badge type={badge}>{status}</Badge>
                <Input
                  type='checkbox'
                  className='ml-6'
                  onClick={(e) => handleClick(e)}
                />
              </TableCell>
            </TableRow>
            <TableRow>
              <TableCell>
                <div className='flex items-center text-sm'>
                  <span className='font-semibold ml-2'>Level 400</span>
                </div>
              </TableCell>
              <TableCell>
                <span className='text-sm'>100</span>
              </TableCell>
              <TableCell>
                <Badge type={badge}>{status}</Badge>
                <Input
                  type='checkbox'
                  className='ml-6'
                  onClick={(e) => handleClick(e)}
                />
              </TableCell>
            </TableRow>
          </TableBody>
        </Table>
        <TableFooter></TableFooter>
      </TableContainer>
    );
  }

  return (
    <React.Fragment>
      {/* Modal popup */}
      <Modal isOpen={isModalOpen} onClose={closeModal}>
        <ModalHeader>{id}</ModalHeader>
        <ModalBody>{renderTable()}</ModalBody>
        <ModalFooter>
          <Button
            className='w-full sm:w-auto'
            layout='outline'
            onClick={closeModal}>
            Cancel
          </Button>
          <Button className='w-full sm:w-auto'>Accept</Button>
        </ModalFooter>
      </Modal>

      <MaterialTable
        icons={tableIcons}
        columns={[
          { title: "Student ID", field: "studentId" },
          { title: "Level", field: "level" },
          { title: "Programme", field: "programme" },
        ]}
        data={[
          {
            studentId: "UG0222021",
            level: "200",
            programme: "BCOM",
          },
          {
            studentId: "UG0199821",
            level: "200",
            programme: "BCOM",
          },
        ]}
        title='Students Information'
        onRowClick={(event, rowData) => openModal(rowData)}
      />
    </React.Fragment>
  );
};
export default Admin;
hamdan
  • 203
  • 1
  • 5
  • 12

2 Answers2

1

Here's a more general purpose way of doing this, I've taken the liberty to use typescript since it's so much easier to show what is happening.

 const initialStatuses = {
  inputLevel100: "pending",
  inputLevel200: "pending",
  inputLevel300: "pending",
  inputLevel400: "pending",
 }

 const initialBadges = {
  inputLevel100: "danger",
  inputLevel200: "danger",
  inputLevel300: "danger",
  inputLevel400: "danger",
 }

 const [status, setStatus] = React.useState<{ [key: string]: string }>(initialBadges);
 const [badge, setBadge] = React.useState<{ [key: string]: string }>(initialStatuses);

 const handleClick = (e: { target: { checked: boolean; name: string} }) => {
 const name = e.target.name;

 if (e.target.checked) {
  setBadge({
    ...badge,
    [name]: "success",
  });
  
  setStatus({
   ...status,
   [name]: "paid",
  });
 }  

}; 

You can get the value of either the status or badge by simply saying

status["Level200"] // Should return either "pending" or "paid"

or

badge["Level200"] // Should return either "danger" or "success"

This should be very performant (see Performance of key lookup in JavaScript object to learn more)

But, more importantly you now have a general purpose way of adding more statuses and badges without having to worry about adding useState for each newly added status or badge

Plus, another added advantage of this approach is that you can (say) get the list of statuses and badges from a remote api or (say) have a statuses.js file that has a list of statuses & a badges.js file on the client side that you can then map over. This then makes your code easier to extend and removing/adding new statuses and badges becomes trivial

Sangeet Agarwal
  • 1,674
  • 16
  • 25
0

It's simply because you're using one state for all status badges. you should use different states to manage every badge. You could add the name attribute to every input so you can see which input is changing in the handleClick function and then update the related state. You could add below code:

/* states */
const[statusOfLevel100, setStatusOfLevel100] = useState('pending')
const[statusOfLevel200, setStatusOfLevel200] = useState('pending')
const[statusOfLevel300, setStatusOfLevel300] = useState('pending')
const[statusOfLevel400, setStatusOfLevel400] = useState('pending')
const[badgeOfLevel100, setBadgeOfLevel100] = useState('danger')
const[badgeOfLevel200, setBadgeOfLevel200] = useState('danger')
const[badgeOfLevel300, setBadgeOfLevel300] = useState('danger')
const[badgeOfLevel400, setBadgeOfLevel400] = useState('danger')
...
/* handleClick function */
const handleClick = (e) => {
switch (e.target.name) {
  case 'inputLevel100':
    if(e.target.checked) {
      setStatusOfLevel100('paid')
      setBadgeOfLevel100('success')
    } else {
      setStatusOfLevel100('pending')
      setBadgeOfLevel100('danger')
    }
    break;
case 'inputLevel200':
    if(e.target.checked) {
      setStatusOfLevel200('paid')
      setBadgeOfLevel200('success')
    } else {
      setStatusOfLevel200('pending')
      setBadgeOfLevel200('danger')
    }
    break;
case 'inputLevel300':
    if(e.target.checked) {
      setStatusOfLevel300('paid')
      setBadgeOfLevel300('success')
    } else {
      setStatusOfLevel300('pending')
      setBadgeOfLevel300('danger')
    }
    break;
case 'inputLevel400':
    if(e.target.checked) {
      setStatusOfLevel400('paid')
      setBadgeOfLevel400('success')
    } else {
      setStatusOfLevel400('pending')
      setBadgeOfLevel400('danger')
    }
    break;
}
ghazal khaki
  • 634
  • 3
  • 9
  • 1
    This only works partially. When I click a checkbox of a row it works but also effects the checkbox of the other rows. So say if i clicked the checkbox of level100, it changes from pending to paid but the level100 of other rows are also affected because they all use the same state. – hamdan Dec 16 '21 at 22:08