1

I have a table (parent element) which fetches users and render each row as it's own component. Row in a table contains checkbox.

Goal is to be able to use checkboxes and retrieve checked checkboxes.

Problem is that when i'm passing function to each row (child component), to trigger adding checked checkbox value to the array (which consists of id's of selected users), whole component gets re-rendered and it is really laggy on the front-end (obviously). I tried to create refs for every checkbox to retrieve checked checkboxes only when needed - but you can't use Refs in a loop and seem to be not a good thing to do for such a task.

My question is how to handle lots of checkboxes in one table, storing checked checkboxes in a state or being able to retrieve checked checkboxes when triger a function in parent component?

const UserRow = ({checked, setChecked) => (
  // render fields
  // ...
  <Checkbox checked={checked} onChange={() => setChecked()} />
)

const ParentComponent = () => {
const [users, setUsers] = useState();
const [checkedCheckboxes, setCheckedCheckboxes] = useState([]);

useEffect(() => {
  // fetch users
  setUsers(fetchedUsers);
});

const rows = users.map(user => <UserRow
        key={id}
        name={user.name}
        email={user.email}
        checked={checkedCheckboxes.includes(id)}
        setChecked={setCheckedCheckboxes(checkedCheckboxes.concat(id)}
      />;

return (
  // render table
  {rows} // inset rows
)}
Src
  • 5,252
  • 5
  • 28
  • 56
  • can you post some code, or some working example ? – Gabriele Petrioli Sep 29 '19 at 19:36
  • Are you using class or functional components and can you use react hooks? – HMR Sep 29 '19 at 19:37
  • @HMR i'm using functional components with hooks. – Src Sep 29 '19 at 19:38
  • Checkout this post - https://itnext.io/handling-large-lists-and-tables-in-react-238397854625 You might find it useful. – pritam Sep 29 '19 at 19:46
  • Why track all checkbox state to begin with? Why not just start with an empty array and add selected keys as they are checked? I also remember reading about an issue with checkboxes causing unncessary re-renders. You may wish to wrap your checkboxes in a [memoized](https://reactjs.org/docs/react-api.html) component to try preventing any excessive re-rendering. – Chris B. Sep 29 '19 at 19:48
  • Can the user not uncheck a row? – HMR Sep 29 '19 at 19:50
  • @RutherfordWonkington that's the approach that i'm using, starting with an empty array. Also memoization won't help much in this case, because you still have to render 200+ items – Src Sep 29 '19 at 19:50
  • @HMR this is the example, functionality described in the post above. So yes, logic a bit more complex and you can uncheck the checkbox retrieving index position in array and splice it. – Src Sep 29 '19 at 19:52

1 Answers1

2

Here is an optimized function that will toggle an item without re rendering the entire list and using a list from useState:

import React, { useEffect, useState } from 'react';

const Parent = () => {
  //list is created and maintained in parent
  const [list, setList] = useState([
    { id: 1, val: true },
    { id: 2, val: true },
  ]);
  //toggle is id to indicate what item to toggle
  const [toggle, setToggle] = useState();
  useEffect(() => {
    if (toggle) {
      //setting toggle will not re render until
      //  function is finished so no infinite loop
      setToggle();
      //here we can toggle the item from the list
      setList(
        list.map(item =>
          item.id === toggle
            ? { ...item, val: !item.val }
            : item
        )
      );
    }
  }, [list, toggle]);
  //combining parent container with parent presentational
  //  for simplicity
  return (
    <div>
      {list.map(item => (
        <ItemContainer
          key={item.id}
          item={item}
          //pass setToggle as item clicked
          itemClicked={setToggle}
        />
      ))}
    </div>
  );
};
const ItemContainer = React.memo(function Item({
  item,
  itemClicked,
}) {
  //toggeling item with id 1 will not log
  //  for item with id 2
  console.log('in item render:', item.id);
  return (
    <pre onClick={() => itemClicked(item.id)}>
      {JSON.stringify(item, undefined, 2)}
    </pre>
  );
});

export default Parent;

Here is an example of how to do it with useReducer.

HMR
  • 37,593
  • 24
  • 91
  • 160
  • 1
    I've found a solution using useReducer + useMemo + useCallback. Going to post my solution soon. Thank you for your time! – Src Sep 30 '19 at 10:07
  • @Src Would love to see your solution (even though this is a year old) – drichar Sep 01 '20 at 23:51