How can I render/update my table using REACT?

Viewed 672

I'm not able to do multi cross filtering, when I click on Apply (to apply all my selected options from my dropdowns) or Cancel button (to reset the selected options). For example filter by taste and availability (please see the picture). But I'm not able to render the filtered rows/updated table.

export default function MenuDisplay() {
  const { menuId } = useParams();
  const { match } = JsonData;
  const [selected, setSelected] = useState({});
  const [hidden, setHidden] = useState({});
  const [taste, setTaste] = useState([
    { label: "Good", value: "Good", name: "Good", selected: false },
    { label: "Medium", value: "Medium", name: "Medium", selected: false },
    { label: "Bad", value: "Bad", name: "Bad", selected: false }
  ]);

  const [comments, setComments] = useState([
    { label: "0", value: "0", name: "0", selected: false },
    { label: "1", value: "1", name: "1", selected: false },
    { label: "2", value: "2", name: "2", selected: false },
    { label: "3", value: "3", name: "3", selected: false },
    { label: "4", value: "4", name: "4", selected: false },
    { label: "5", value: "5", name: "5", selected: false }
  ]);

  const [availability, setAvailability] = useState([
    {
      label: "availability",
      value: "availability",
      name: "Availability",
      selected: false
    },
    { label: "trust", value: "trust", name: "Trust", selected: false }
  ]);

  function selectionOpt(setItems) {
    return (selection) => {
      setItems(selection);
    };
  }

  const impact = (value) => {
    if (value === 1) {
      return (
        <div>
          <TaskAltIcon />
        </div>
      );
    } else {
      return (
        <div>
          <CancelIcon />
        </div>
      );
    }
  };

  // If any row is selected, the button should be in the Apply state
  // else it should be in the Cancel state
  const buttonMode = Object.values(selected).some((isSelected) => isSelected)
    ? "apply"
    : "cancel";

  const rowSelectHandler = (id) => (checked) => {
    setSelected((selected) => ({
      ...selected,
      [id]: checked
    }));
  };

  const handleClick = () => {
    if (buttonMode === "apply") {
      // Hide currently selected items
      const currentlySelected = {};
      Object.entries(selected).forEach(([id, isSelected]) => {
        if (isSelected) {
          currentlySelected[id] = isSelected;
        }
      });
      setHidden({ ...hidden, ...currentlySelected });

      // Clear all selection
      const newSelected = {};
      Object.keys(selected).forEach((id) => {
        newSelected[id] = false;
      });
      setSelected(newSelected);
    } else {
      // Select all currently hidden items
      const currentlyHidden = {};
      Object.entries(hidden).forEach(([id, isHidden]) => {
        if (isHidden) {
          currentlyHidden[id] = isHidden;
        }
      });
      setSelected({ ...selected, ...currentlyHidden });

      // Clear all hidden items
      const newHidden = {};
      Object.keys(hidden).forEach((id) => {
        newHidden[id] = false;
      });
      setHidden(newHidden);
    }
  };

  const matchData = (
    match.find((el) => el._id_menu === menuId)?._ids ?? []
  ).filter(({ _id }) => {
    return !hidden[_id];
  });

  const getRowProps = (row) => {
    return {
      style: {
        backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
      }
    };
  };

  const data = [
    {
      Header: "id",
      accessor: (row) => row._id
    },
    {
      Header: "Name",
      accessor: (row) => (
        <Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
      )
    },
    {
      Header: "Taste",
      accessor: (row) => row.taste
    },
    {
      Header: "Comments",
      //check current row is in hidden rows or not
      accessor: (row) => {
        const comments = parseInt(row.comments, 10);

        return <Counter count={comments} />;
      }
    },
    {
      Header: "Price",
      accessor: (row) => row.price,
      id: "price"
    },
    {
      Header: "Status",
      accessor: (row) => row.status
    },
    {
      Header: "Availability",
      accessor: (row) => row.availability,
      id: "availability",
      Cell: (props) => impact(props.value)
    },
    {
      Header: "Trust",
      accessor: (row) => row.trust,
      id: "trust",
      Cell: (props) => impact(props.value)
    },
    {
      Header: "Show",
      accessor: (row) => (
        <Toggle
          value={selected[row._id]}
          onChange={rowSelectHandler(row._id)}
        />
      )
    }
  ];

  const initialState = {
    sortBy: [
      { desc: false, id: "id" },
      { desc: false, id: "description" }
    ],
    hiddenColumns: ["dishes", "id"]
  };

  return (
    <div>
      <button type="button" onClick={handleClick}>
        {buttonMode === "cancel" ? "Cancel" : "Apply"}
      </button>
      <div className="flex justify-end gap-4 ">
        <div>
          <Button>Apply</Button>
        </div>
        <div>
          <Button>Cancel</Button>
        </div>
      </div>
      Taste
      <ListDrop
        placeholder={"Select"}
        items={taste}
        onSelect={selectionOpt(setTaste)}
        hasAll
      />
      Comments
      <ListDrop
        placeholder={"Select"}
        items={comments}
        onSelect={selectionOpt(setComments)}
        hasAll
      />
      <p>Availability & Trust </p>
      {/* I would like to have in my dropdown Availability and Trust as 
           options in my dropdown and it refers to the cross where availaibility: 1 and trust:1 ) */}
      <ListDrop
        placeholder={"Select"}
        items={availability}
        onSelect={selectionOpt(setAvailability)}
        hasAll
      />
      <Table
        data={matchData}
        columns={data}
        initialState={initialState}
        withCellBorder
        withRowBorder
        withSorting
        withPagination
        rowProps={getRowProps}
      />
    </div>
  );
}

Please check my codeSandbox

Please check the picture to get an idea :

enter image description here

2 Answers

I choose some of the fields and create a simple App for you, if you read the code carefully, you will figure out how it works and then you can change your code similarly :

table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}

td,
th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}

tr:nth-child(even) {
  background-color: #dddddd;
}

import React, { useEffect } from "react";
import "./styles.css";

const soupsData = [
  {
    name: "Pea Soup",
    taste: "Good",
    trust: 1,
    availability: 0
  },
  {
    name: "Potato Soup ",
    taste: "Medium",
    trust: 1,
    availability: 1
  },
  {
    name: "Cucumber Soup ",
    taste: "Medium",
    trust: 1,
    availability: 1
  }
];

const tasteOptions = [
  { label: "Good", name: "Good" },
  { label: "Medium", name: "Medium" },
  { label: "Bad", name: "Bad" }
];

const availabilityAndTrustOptions = [
  {
    name: "all",
    label: "all"
  },
  {
    name: "availability",
    label: "availability"
  },
  {
    name: "trust",
    label: "trust"
  }
];

function App() {
  const [soups, setSoups] = React.useState([]);
  const [tastes, setTastes] = React.useState([]);
  const [availabilityAndTrust, setAvailabilityAndTrust] = React.useState({
    trust: 0,
    availability: 0
  });

  useEffect(() => {

    const filteredSoups = soupsData.filter((soup) => {
      const availability = availabilityAndTrust.availability;
      const trust = availabilityAndTrust.trust;

      if (availability) {
        if (!soup.availability) return false;
      }

      if (trust) {
        if (!soup.trust) return false;
      }

      if (tastes.length > 0) {
        if (!tastes.includes(soup.taste)) return false;
      }

      return true;
    });

    setSoups(() => filteredSoups);
  }, [tastes, availabilityAndTrust]);

  const handleTasteChange = (e) => {
    const targetTaste = e.target.name;
    if (tastes.includes(targetTaste)) {
      setTastes(() => tastes.filter((taste) => taste !== targetTaste));
    } else setTastes((prevData) => [...prevData, targetTaste]);
  };

  const handleAvailabilityAndTrustChange = (e) => {
    const targetName = e.target.name;
    switch (targetName) {
      case "all":
        setAvailabilityAndTrust((prevData) => {
          if (prevData.trust && prevData.availability) {
            return {
              trust: 0,
              availability: 0
            };
          }
          if (prevData.trust || prevData.availability) {
            return {
              trust: 1,
              availability: 1
            };
          }
          return {
            trust: 1,
            availability: 1
          };
        });
        break;
      case "availability":
        setAvailabilityAndTrust((prevData) => {
          return {
            trust: prevData.trust,
            availability: prevData.availability ? 0 : 1
          };
        });
        break;
      case "trust":
        setAvailabilityAndTrust((prevData) => {
          return {
            trust: prevData.trust ? 0 : 1,
            availability: prevData.availability
          };
        });
        break;
      default:
    }
  };

  return (
    <div>
      <div>
        <div>Taste</div>
        {tasteOptions.map((taste) => {
          return (
            <div>
              <label for={taste.label}>{taste.label}</label>
              <input
                name={taste.name}
                onChange={handleTasteChange}
                id={taste.label}
                type="checkbox"
              />
            </div>
          );
        })}
      </div>
      <br />
      <div>
        <div>Availability & Trust</div>
        {availabilityAndTrustOptions.map((option) => {
          return (
            <div>
              <label for={option.label}>{option.label}</label>
              <input
                name={option.name}
                checked={(() => {
                  switch (option.label) {
                    case "all":
                      return (
                        availabilityAndTrust.availability &&
                        availabilityAndTrust.trust
                      );
                    case "availability":
                      return availabilityAndTrust.availability;
                    case "trust":
                      return availabilityAndTrust.trust;
                    default:
                      return false;
                  }
                })()}
                onChange={handleAvailabilityAndTrustChange}
                id={option.label}
                type="checkbox"
              />
            </div>
          );
        })}
      </div>
      <br />
      <table>
        <tr>
          <th>Name</th>
          <th>Taste</th>
          <th>Trust</th>
          <th>Availability</th>
        </tr>
        {soups.map((soup) => {
          return (
            <tr>
              <td>{soup.name}</td>
              <td>{soup.taste}</td>
              <td>{soup.trust}</td>
              <td>{soup.availability}</td>
            </tr>
          );
        })}
      </table>
    </div>
  );
}

you should use useState for storing data that might change during runtime and has a rendering effect.

I used useEffect to filter the table's content according to the options change.

codesandbox : https://codesandbox.io/s/serene-shamir-lns51i

I wrote three function for Apply and Cancel Functionality

`const applyFilters = () => {
    const allData = match.find((el) => el._id_menu === menuId)?._ids ?? [];
    let temp = allData.filter((entity) => {
      const selectedTaste = taste.find((entity) => entity.selected);
      const selectedComments = comments.find((entity) => entity.selected);
      let condition = true;
      if (selectedTaste && selectedTaste.value !== entity.taste) {
        condition = false;
      }
      if (selectedComments && selectedComments.value !== entity.comments) {
        condition = false;
      }
      return condition;
    });
    setFilteredData(temp);
  };

  const resetFilters = (filterList) =>
    filterList.map((entity) => ({
      ...entity,
      selected: false
    }));

  const cancelClickhandler = () => {
    setTaste(resetFilters(taste));
    setAvailability(resetFilters(availability));
    setComments(resetFilters(comments));
    setFilteredData(match.find((el) => el._id_menu === menuId)?._ids ?? []);
  };`

Apply and Cancel Functionaly is working as expected. I have also made few more changes in your codesandBox, 1- added onclick prop in Button Component

<div>
   <Button onClick={applyFilters}>Apply</Button>
 </div>
 <div>
   <Button onClick={cancelClickhandler}>Cancel</Button>
 </div>

2- added on state variable for storing filteredData

  `const [filteredData, setFilteredData] = useState(
    match.find((el) => el._id_menu === menuId)?._ids ?? []
  );`

3- modified matchdata return value

`const matchData = filteredData.filter(({ _id }) => {
  return !hidden[_id];
});`

Please look into modified sandbox https://codesandbox.io/s/filters-forked-lq0ni4

Related