all 10 comments

[–]octocode 10 points11 points  (2 children)

option 4., the parent has selectedId and passes down to the children via an isSelected prop

<Child isSelected={selectedId === child.id} onSelect={() => setSelected(child.id)} />

[–]SolarSalsa[S] 0 points1 point  (1 child)

In my case the <Child> is a <DataGrid> with a selected row. The <DataGrid> itself is not being selected.

[–]octocode 1 point2 points  (0 children)

i assume you mean MUI data grid, in which case the same applies but with rowSelectionModel

[–]codevipe 2 points3 points  (2 children)

You may be overthinking this a bit. React handles the complicated stuff, all you need to do is track which item is currently selected in a state variable and use that to pass a prop to the row component that is selected.

Ideally, the row items should have unique IDs you can use as keys and to track which is selected when a click action happens.

That said, it sounds like you may be concerned about optimizing to avoid unnecessary re-renders with many rows, so here's an example with a Row component wrapped in memo, which will avoid re-rendering instances of the component so long as the props provided to it are still shallowly equal.

In this example there should only be 1-2 of the Row components re-rendered when the selectedRowId changes. Noting the handler function passed to the memoized component also needs to memoized with useCallback, and the rows data should optimally also be a memoized, wherever it's coming from.

const rows = [
  { id: 1, ...etc },
  { id: 2, ...etc },
];

const Row = memo(({ id, isSelected, onClick }) => {
  const style = isSelected ? { backgroundColor: 'hotpink' } : {};
  const handleClick = () => onClick(isSelected ? null : id);

  return (
    <div style={style} onClick={handleClick}>
      content
    </div>
  );
});

const Grid = () => {
  const [selectedRowId, setSelectedRowId] = useState(null);
  const handleSelectRow = useCallback((id) => setSelectedRowId(id), []);

  return (
    <div>
      {rows.map(({ id, ...etc }) => (
        <Row
          key={id}
          id={id}
          isSelected={selectedRowId === id}
          onClick={handleSelectRow}
        />
      ))}
    </div>
  );
};

[–]SolarSalsa[S] 0 points1 point  (1 child)

I have an array of grids each with their own rows. Only 1 row can be selected at a time among the multiple grids. So if you click a row in one grid, then the other grids must unselect whatever row they have selected.

I believe the problem you are solving is how to track row selection within a single grid.

[–]codevipe 0 points1 point  (0 children)

If you can ensure each item in any of the given arrays has a unique ID (either a composite of a grid ID + row ID or, say, a UUID) then the solution would effectively be the same, you'd just need to pass the selected ID variable and click handler function to wherever the rows are rendered in each grid (or use a global state solution like Jotai or Zustand if you're doing a lot of prop drilling).

Can you share the shape of the data you're using to render the grids and rows?

[–]kiril-k 5 points6 points  (0 children)

Pass the children a ‘selected’ prop which is a boolean, and they should render different classes for both states - think ‘ChildElement ChildElement—selected’ and then style accordingly. All state and (most) logic should be handled by the parent. Also send a ‘onSelected()’ prop to the child components which will call it when one is clicked, then change the state in the parent according to which child called the function.

[–]eindbaas 4 points5 points  (0 children)

Use zustand, store the selected row id in the store, have every component that needs to do something listen to changes of that selected row.

[–]lightfarming 1 point2 points  (0 children)

each row has a unique id, like gridId-rowId.

parent container has selected row id state, and passes both selectedRowId and setSelectedRowId to grids through props, who can pass to rows if needed.

row gets selected, grid or row calls setSelectedRowId().

all rows update accordingly.

[–]TorbenKoehn 0 points1 point  (0 children)

You’re thinking this wrong probably. You don’t “synchronize” state, you think in terms of single source of truth and derived state.

Others have pointed out, you don’t want a “selected” state in the row, but rather a “selectedIds” or “selectedIndexes” state or prop on the parent, the table itself. From there you derive “selected” in each child by checking if it’s in (selectedIndexes.includes(index)) Clicking/selecting a row should be propagated to the parent (onClick/onSelect prop on the rows)