-1

I am trying to make a selection system with checkboxes including a master checkbox on the top of the table. I am facing a problem with selection. Only the master toggle is working and others are not working. Actually, the code is running and a selected log is updating but UI is not updating. Please help me.

Here is codesandbox link: link

import TableHead from "./Table_Head";
import TableContent from "./Table_Content";
import Paginator from "./Paginator";
import TopAction from "./Top_Action";
import FilterMenu from "./Filter_Menu";
import UserDetail from "./User_Detail";
import "./Table.css";

import { useState, useRef, useEffect } from "react";
import Users from "./Data";

const TableMaker = (props) => {
  return (
    <TableContent
      keyVal={props.data.sn}
      Full_Name={props.data.Full_Name}
      User_Id={props.data.User_Id}
      Points={props.data.Points}
      Status={props.data.Status}
      updateSelectLog={() => props.updateSelectLog()}
      isSelected={props.isSelected}
    />
  );
};

export default function Table() {
  const selectLog = useRef([]);
  const selAll = useRef(false);

  console.log("first array ", selectLog.current);

  selectLog.current.length === 0
    ? Users.forEach((i) => selectLog.current.push(false))
    : console.log("already added");

  const [log, setLog] = useState(selectLog.current);

  // console.log("log: ", selectLog.current, log);


  //useEffect(() => {}, []); // set the selection log at start

  const updateSelectLog = (i) => {
    console.log("clicked button: ", i);

    if (i !== "master") {
      let t = selectLog.current;
      t[i] = !t[i];
      console.log("changed value: ", selectLog.current[i]);
      setLog(t);
    } else {
      selAll.current = !selAll.current;
      selectLog.current = selectLog.current.map(() => selAll.current);
      setLog(selectLog.current);
    }
  };
  const isAllSelected = () => {
    console.log("is all selected", log);
    return log.indexOf(false) === -1 ? true : false;
  };
  // const isSelected=(i)=>log[i];

  return (
    <>
      <div className="Table_Container">
        {/* <FilterMenu/> */}
        {/* <TopAction /> */}
        <TableHead
          updateSelectLog={() => updateSelectLog("master")}
          isAllSelected={isAllSelected()}
        />
        {Users.map((i) => (
          <TableMaker
            key={i.sn}
            data={i}
            isSelected={log[i.sn]}
            updateSelectLog={() => {
              updateSelectLog(i.sn);
            }}
          />
        ))}
        {/* <Paginator /> */}
        {/* <UserDetail/> */}
      </div>
    </>
  );
}
  • 1
    Hi! Please put your runnable example **here, on-site**, not just linked. Stack Snippets support React, including JSX; [here's how to do one](http://meta.stackoverflow.com/questions/338537/). Three reasons: People shouldn't have to go off-site to help you; some sites are blocked for some users; and links rot, making the question and its answers useless to people in the future. – T.J. Crowder Aug 04 '21 at 18:12
  • Also, please **reduce** the example to just what's necessary to replicate the problem. There's a lot of extra stuff above, making it hard to see where the problem might be. (More about why this is both important and useful to you in that MCVE link.) Please also tell us **what** state you're talking about, where you think it's being set, etc. People will be glad to help. – T.J. Crowder Aug 04 '21 at 18:14
  • If you're talking about the `setLog` code in this sequence: `let t = selectLog.current; t[i] = !t[i]; console.log("changed value: ", selectLog.current[i]); setLog(t);` the problem is that you're breaking one of the rules of React state: You're modifying a state item directly, not creating a new state item. The correct way to update an array is (for instance) `setLog(log => [...log, newEntry])` (to add) or `setLog(log => { log = [...log]; log[i] = updatedEntry; return log; })` (to update). You can't keep an array in a ref, use it as state, and modify it, that won't work (as you've seen). – T.J. Crowder Aug 04 '21 at 18:16
  • 1
    Probably a duplicate of [this](https://stackoverflow.com/questions/41446560/react-setstate-not-updating-state) and/or [this](https://stackoverflow.com/questions/26253351/correct-modification-of-state-arrays-in-react-js) and/or other similar. – T.J. Crowder Aug 04 '21 at 18:18

3 Answers3

0

When you use useState and call the setter with a value, it'll only trigger a re-render if Object.is(oldValue, newValue) is false. You are modifying the state array, then set it to the same array, hence React thinks "it's the same value, no need to re-render".

A simple fix is to clone the array, etc:

      let t = selectLog.current;
      t[i] = !t[i];
      console.log("changed value: ", selectLog.current[i]);
      setLog(t); // setting it to the same array (even though you mutated it)

Although you'd be better off not modifying the old state at all:

      let t = [...selectLog.current];
      t[i] = !t[i];
      console.log("changed value: ", t[i]);
      setLog(t); // new reference to a new array

Apart from that issue, it's a bit strange you're using useRef and useState for basically the same variable. Maybe I just overlooked a certain reason for that.

Kelvin Schoofs
  • 8,323
  • 1
  • 12
  • 31
  • This issue is **well-covered** by previous questions and answers. if this is the problem, vote to close as a duplicate, don't post an answer. For instance, [this](https://stackoverflow.com/questions/41446560/react-setstate-not-updating-state) and/or [this](https://stackoverflow.com/questions/26253351/correct-modification-of-state-arrays-in-react-js). – T.J. Crowder Aug 04 '21 at 18:17
  • Thankyou so much – Laxman Deadpool Aug 05 '21 at 04:03
0

In addition to modifying the array in place, your code has an unnecessary useRef to maintain the booleans. You can just use useState to maintain it, as in the following code:

  const [log, setLog] = useState(() => Users.map((i) => false));

  const updateSelectLog = (i) => {
    console.log("clicked button: ", i);

    if (i !== "master") {
      const newLog = [...log];
      newLog[i] = !newLog[i];
      setLog(newLog);
    } else {
      selAll.current = !selAll.current;
      setLog(log.map(() => selAll.current));
    }
  };

Also, to prevent your checkboxes from rerendering every time the state changes, you should bring the TableMaker component out to the top level; otherwise, it gets replaced by a new component every render.

CodeSandbox link

edemaine
  • 2,699
  • 11
  • 20
0

Also an alternate solution would be adding slice to the array to make a copy.

if (i !== "master") {
  let t = log.slice();;
  t[i] = !t[i];
  console.log("changed value: ", t);
  setLog(t);
}

Refer this for detailed explaination

Neel Dsouza
  • 1,342
  • 4
  • 15
  • 33