0

I have a React component that acts as a sortable table. The table header and the table rows are children of a container, and the container is handling the state of the table. When a header is clicked, the data is re-ordered just like it is in this semantic-ui-react example.

handleSort = (clickedColumn) => {
const { column, orders, direction } = this.state

if (column !== clickedColumn) {
  this.setState({
    column: clickedColumn,
    orders: customSort(orders, clickedColumn),
    direction: 'ascending',
  })
} else {
this.setState({
  orders: orders.reverse(),
  direction: direction === 'ascending' ? 'descending' : 'ascending',
})}

The first time I click a column header, the first closure runs, and this.setState changes the state of the container, and triggers the children to receive new props and update accordingly. If I re-click the column header to reverse the order of the data, the second closure runs, and this.setState updates the state of the container. Accordingly, the child component OverviewTableHeader updates, but OverviewTableRows does not.

render() {
const { designers, orders, column, direction } = this.state
if (orders === "loading"){
  return <Loading />
} 
const tableRows = <OverviewTableRows  
                     designers={designers} 
                     orders={this.state.orders}
                     />
debugger
return (
  <div className="overview">
    <Table selectable fixed sortable>
      <OverviewTableHeader sort={this.handleSort} column={column} direction={direction}/>
      {tableRows}
    </Table>
  </div>
  ) 
}

Here's a video of this in action.

In the video, you can see OverviewTableRows trigger componentWillReceiveProps and shouldComponentUpdate the first time setState is triggered in the parent, but not the second.

I can add all of my code if needed. Is this a bug? Any help is greatly appreciated!

2 Answers2

1

I solved this by making a copy of the array before reversing it and using it to update the state.

handleSort = (clickedColumn) => {
const { column, orders, direction } = this.state

if (column !== clickedColumn) {
  this.setState({
    column: clickedColumn,
    orders: customSort(orders, clickedColumn),
    direction: 'ascending',
  })
} else {
  const reversedOrders = orders.slice().reverse();
this.setState({
  orders: reversedOrders,
  direction: direction === 'ascending' ? 'descending' : 'ascending',
})}

I guess the identity of the array orders mattered. I'm guessing this has to do with the functional nature of React. I hope this helps somebody! If anybody has a good explanation of why this is the case, I'd love to hear it.

0

Here's a good quote that may explain what's going on.

By manipulating this.state directly you are circumventing React’s state management, which can be potentially dangerous as calling setState() afterwards may replace the mutation you made.

Taken from this article.

Since orders is an mutable object and a member of state, you are altering the state directly when you call orders.reverse (even if this reordering is being done within a setState call).

So, yes, your solution of creating a copy of orders will solve this issue because you are no longer altering this.state directly.

Jared Goguen
  • 8,772
  • 2
  • 18
  • 36