React useState with sorted array does not cause re-render of SemanticUI table if initial sorting is done

馋奶兔 提交于 2021-01-29 20:19:49

问题


I have a table with SemanticUI that renders a list of companies and allows the list to be resorted based on clicking the header of the table. I thought I had this wrapped up working fine (the dev env is sending me a pre-sorted list), but when writing tests, I realized I cannot guarantee the list is pre-sorted.
So I edited my initial useState logic to have a sort function which is the same logic for the header click:

BEFORE (works): const [data, setData] = useState(companies);

AFTER (does not work): const [data, setData] = useState(companySortData(companies, COLUMN_NAME.NAME));

The initial UI looks OK with both above, but when I click the header, this is where the issue appears: the UI does not change (re-render).

In my stepping through the code, I see that the correctly RE-sorted array is there, but somewhere in the React internals, the array does not trigger a state change.

I have tried a plethora of options including: 1) changing the state so that the data is part of the sortedColumnData, 2) making an useEffect hook that allows for the initial company data to be there and then we setData. 3) trying to create a usePrevious hook that compares the sortedColumnData and then calls setData if that dependency changes. 4) changed from a Functional to PureComponent, but this isn't fully solving it either.

I know that React only shallow diffs so that might be part of it, but the data array shape itself is shallow so I thought this would be picked up (which is does if I do not initial sort data):

{
  id: "newnew",
  name: "A New New Company",
  subdomain: "newnew",
  createdAt: "2019-10-28T21:54:30.253Z",
  updatedAt: "2019-10-28T21:54:30.253Z"
},

So, here is a CodeSandBox link: https://codesandbox.io/s/wonderful-thompson-vg2x1

Here is the component itself:

export const CompanyTable = ({ companies }) => {
  const [sortedColumnData, setSortedColumnData] = useState({
    data: companySortData(companies, COLUMN_NAME.NAME), // swap this initial sorting with just the `companies` and this table works fine
    direction: DIRECTION.ASCENDING,
    columnName: COLUMN_NAME.NAME
  });

  const handleClick = clickedColumn => {
    if (!clickedColumn) return;

    if (sortedColumnData.columnName !== clickedColumn) {
      setSortedColumnData({
        data: companySortData(sortedColumnData.data, clickedColumn),
        columnName: clickedColumn,
        direction: DIRECTION.ASCENDING
      });
      return;
    }

    setSortedColumnData({
      data: sortedColumnData.data.reverse(),
      columnName: clickedColumn,
      direction:
        sortedColumnData.direction === DIRECTION.ASCENDING
          ? DIRECTION.DESCENDING
          : DIRECTION.ASCENDING
    });
  };
  return (
    <Table celled selectable sortable>
      <Table.Header>
        <Table.Row>
          {headerCells.map(cell => (
            <Table.HeaderCell
              key={`cell-${cell.testid}`}
              sorted={
                sortedColumnData.columnName === cell.clickHandlerText
                  ? sortedColumnData.direction
                  : undefined
              }
              data-testid={`header-${cell.testid}`}
              onClick={() => handleClick(cell.clickHandlerText)}
            >
              {cell.title}
            </Table.HeaderCell>
          ))}
        </Table.Row>
      </Table.Header>
      <Table.Body data-testid="company-table-body">
        {sortedColumnData.data.map(
          ({ id, name, subdomain, createdAt, updatedAt }) => {
            return (
              <Table.Row
                key={id}
                role="link"
                data-testid={id}
                style={{ cursor: "pointer" }}
              >
                <Table.Cell singleLine>{name}</Table.Cell>
                <Table.Cell singleLine>{subdomain}</Table.Cell>
                <Table.Cell singleLine>{createdAt}</Table.Cell>
                <Table.Cell singleLine>{updatedAt}</Table.Cell>
              </Table.Row>
            );
          }
        )}
      </Table.Body>
    </Table>
  );
};

I am not sure why the re-rendering from setting state with the re-sorted array is failing...


回答1:


Its happening because you passed the reference of your props to sort function and trying to set it in state, you cannot mutate a prop, its the fundamental of React. If you want to store a prop into state kill the reference first. Hope it answers your doubt :)

  const companySortData = (data, sortBy) => {
  let copyData = JSON.parse(JSON.stringify(data));
  return copyData.sort((a, b) => {
    let x = a[sortBy];
    let y = b[sortBy];
    if (sortBy === COLUMN_NAME.CREATED || sortBy === COLUMN_NAME.UPDATED) {
      x = new Date(a.createdAt);
      y = new Date(b.createdAt);
    } else {
      x = a[sortBy].toLowerCase();
      y = b[sortBy].toLowerCase();
    }
    return x < y ? -1 : x > y ? 1 : 0;
  });
};


来源:https://stackoverflow.com/questions/59272240/react-usestate-with-sorted-array-does-not-cause-re-render-of-semanticui-table-if

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!