sn3ll
sn3ll

Reputation: 1685

Changing order of React components only triggers re-render after second click

So I am mapping an array to a list group to display a list of items. I want to be able to sort that list by any property of the items. I am able to resort the array easy enough, and I have a Reactstrap dropdown that triggers the sort, and the arrays sorts properly and even gets updated in the component properly. However, the component does not re-render. HOWEVER, if I click the button to open the dropdown list again, the component THEN re-renders. I'm baffled. Any thoughts?

Function in the container component file that does the sorting (uses another external utility function, again this works fine):

  select(event) {
    this.setState({
      sortBy: event.target.innerText
    }, function () {
        const propName = this.state.sortBy.toLowerCase();
        this.state.dives.sort(Utilities.sortByParam(propName));
    });
  }

This is the presentational component:

const DiveList = (props) => {
  const {
    dives,
    toggle,
    select,
    isOpen,
  } = props;
  console.log('dives: ', dives);
  return (
    <div>
      <h3>My Dives 
        <Dropdown size="sm" className="float-right" isOpen={isOpen} toggle={toggle}>
          <DropdownToggle caret>
            Sort By
          </DropdownToggle>
          <DropdownMenu right>
            <DropdownItem onClick={select}>Number</DropdownItem>
            <DropdownItem onClick={select}>Location</DropdownItem>
            <DropdownItem onClick={select}>Date</DropdownItem>
          </DropdownMenu>
        </Dropdown>
      </h3>
      {dives.length > 0 &&
        <div>
          <ListGroup>
            {dives.map((dive) => (
              <ListGroupItem key={`dive-${dive.number}`}>
                <Link to={`/divedetails/${dive.number}`}>
                  <div>
                    <span>Dive #{dive.number}</span> 
                    <span className="float-right">{dive.date}</span>
                  </div>
                  <div className="float-left">{dive.location}</div>
                </Link>
              </ListGroupItem>
            ))}
          </ListGroup>
        </div>
      }
      {dives.length <= 0 && 
        <h5>You don't have any dives logged yet.  Time to get wet!!!</h5>
      }
    </div>
  );
};

export default DiveList;

Again, the console log "dives" updates the array immediately when the button is selected (clicked) in the dropdown, but the ListGroup component does not re-render.

If I click the "Sort by" button again (NOT one of the menu buttons, but the toggle button) then the components refreshes.

Help...

Upvotes: 0

Views: 1150

Answers (1)

Brian Hadaway
Brian Hadaway

Reputation: 1893

The problem is that you're a) sorting in the setState callback and b) your sort is mutating dives instead of setting state.dives to a new, sorted array via setState. This is because render isn't called if state mutates directly - only if setState is called. Instead you can perform the sort in the select function and set sortBy and dives in a single setState call.

select(event) {
  const sortBy = event.target.innerText;
  const sortByParam = sortBy.toLowerCase();
  this.setState({
    sortBy,
    dives: [...this.state.dives.sort(Utilities.sortByParam(sortByParam))];
  });
}

Upvotes: 1

Related Questions