When sorting components state of children are not updated

Viewed 43

I have a parent component on which children need to be sorted by date. I have achieved to sort the component, however these component have a state that remain fixed when sorting button is clicked.

Example :

I have table

first row {name : foo, date : 06/16/2022} second row {name : bar, date :06/12/2022}

after sorting

first row {name : foo, date : 06/12/2022} second row {name : bar, date : 06/16/2022}

name remain fixed, date are sorted.

Parent Component

export default class RonusBows extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      bonus: [],
      order: "asc",
    };
    this.sortFunction = this.sortFunction.bind(this);
  }

  state = {
    bonus: [],
    order: "asc",
  };

  componentDidMount() {
    axios
      .get(`http://localhost:4000/api/v1/process/creator/gutierad5`)
      .then((res) => {
        const bonus = res.data;
        console.log(17, bonus);
        this.setState({ bonus: bonus.processes });
      });
  }

  sortFunction() {
    if (this.state.order == "asc") {
      this.setState(
        this.state.bonus.sort((a, b) => {
          const objectA = new Date(a.createdAt);
          const objectB = new Date(b.createdAt);
          return objectB.getTime() - objectA.getTime();
        })
      );
      this.setState({ order: "desc" });
    } else {
      this.setState(
        this.state.bonus.sort((a, b) => {
          const objectA = new Date(a.createdAt);
          const objectB = new Date(b.createdAt);
          return objectA.getTime() - objectB.getTime();
        })
      );
      this.setState({ order: "asc" });
    }
  }

  render() {
    return (
      <ProcessTable funcionality={this.sortFunction}>
        <Accordion allowToggle allowMultiple>
          {this.state.bonus.map((element, index) => (
            <AccordionItemSlot
              key={index}
              proccessID={element.id}
              title={element.name}
              targeruser='gutierad5'
              createDate={FormatDateInYYMMDD(element.createdAt)}
              status={element.status}
              creator={element.creator}
              links={element.links}
              amount={element.processBonus.amount}
              updatedAt={element.updatedAt}
              password={element.processBonus.password}
              franchise={element.processBonus.franchise}
            />
          ))}
        </Accordion>
      </ProcessTable>
    );
  }
}

**children component**

export type AccordionItemType = InputHTMLAttributes<HTMLInputElement> & {
  title: string;
  targeruser: string;
  createDate: string;
  creator?: string;
  status: string;
  links: Array<any>;
  amount: string;
  updatedAt: string;
  password: string;
  franchise: string;
  proccessID: string;
};

export const AccordionItemSlot = (props: AccordionItemType) => {
  const WarningString = `Please Click on Accept to see your price`;
  const StateContext = React.createContext(props.status);

  const [state, setState] = useState(props.status);

  const handleChange = () => {
    setState("Acknowledged");
  };

  return (
    <StateContext.Provider value={state}>
      <AccordionItem>
        <h2>
          <AccordionButton>
            <Box flex='1'>
              <ProcessRow
                status={state}
                title={props.title}
                targeruser={props.targeruser}
                createDate={props.createDate}
                creator={"daniela"}
              />
            </Box>
          </AccordionButton>
        </h2>
        <AccordionPanel pb={4}>
          <HStack spacing='150px'>
            <Box>
              <List spacing={3}>
                <ListItem>
                  <ListIcon as={BiCaretRight} color='green.500' />
                  Title : {props.title}
                </ListItem>
                <ListItem>
                  <ListIcon as={BiCaretRight} color='green.500' />
                  Creator : {props.creator}
                </ListItem>
                <ListItem>
                  <ListIcon as={BiCaretRight} color='green.500' />
                  Created Date : {props.createDate.toString()}
                </ListItem>
                <ListItem>
                  <ListIcon as={BiCaretRight} color='green.500' />
                  {state === "Acknowledged" ? (
                    <a
                      href={checkIfHTTPSOrNot(props.links[0].URL)}
                      target='_blank'
                      rel='noopener'
                    >
                      Click here to check your prize <ExternalLinkIcon mx={2} />
                    </a>
                  ) : (
                    WarningString
                  )}
                </ListItem>
              </List>
            </Box>
            <Box>
              <List spacing={3}>
                <ListItem>
                  <ListIcon as={BiCaretRight} color='green.500' />
                  Amount : {props.amount} COP
                </ListItem>
                <ListItem>
                  <ListIcon as={BiCaretRight} color='green.500' />
                  Password : {props.password}
                </ListItem>
                <ListItem>
                  <ListIcon as={BiCaretRight} color='green.500' />
                  Franchise : {props.franchise}
                </ListItem>
                <ListItem>
                  {state === "New" ? (
                    <ButtonComponent
                      colorScheme={"teal"}
                      variant={"solid"}
                      funcionality={() =>
                        requestForButton(
                          props.proccessID,
                          props.targeruser,
                          handleChange
                        )
                      }
                      size={"md"}
                    >
                      Accept
                    </ButtonComponent>
                  ) : (
                    " You are ready to collect your prize!"
                  )}
                </ListItem>
              </List>
            </Box>
          </HStack>
        </AccordionPanel>
      </AccordionItem>
    </StateContext.Provider>
  );
};
export const AccordionWrapper = ({ children }: { children: ReactNode }) => {
  <Accordion allowMultiple allowToggle>
    {children}
  </Accordion>;
};
```

Thanks in advanced, I'm very new to react and ts : P
2 Answers

Some array methods like Array.sort mutate directly array in memory which cause React not to rerender the component due to no actual change from the original array and new array after sort.

The general rule of thumb is avoiding object or array mutation when the new change will affect components rerender.


function sortFunction() {
  if (this.state.order == "asc") {
    const sortedBonusAsc = [...this.state.bonus].sort((a, b) => {
      const objectA = new Date(a.createdAt);
      const objectB = new Date(b.createdAt);
      return objectB.getTime() - objectA.getTime();
    });

    this.setState({ order: "desc", bonus: sortedBonusAsc });
  } else {
    const sortedBonusDesc = [...this.state.bonus].sort((a, b) => {
      const objectA = new Date(a.createdAt);
      const objectB = new Date(b.createdAt);
      return objectA.getTime() - objectB.getTime();
    });

    this.setState({ order: "asc", bonus: sortedBonusDesc });
  }
}



Issue

The issue is that Array.prototype.sort does an in-place sorting of the array and returns the same array, sorted.

Array.prototype.sort

  1. Don't mutate state in React
  2. React uses shallow reference equality checks as part of the reconciliation process, and since the array reference doesn't change, React bails on rerendering.

Solution

When enqueuing React state updates you should shallow copy all state, and nested state, that is being updated. Create a copy of the array prior to sorting it so it is the new array that get's sorted.

Example:

ascendingComparator = (a, b) => {
  const objectA = new Date(a.createdAt);
  const objectB = new Date(b.createdAt);
  return objectB.getTime() - objectA.getTime();
}

descendingComparator = (a, b) => {
  return ascendingComparator(b, a);
}

sortFunction = () => {
  this.setState(state => ({
    bonus: state.bonus
      .slice() // <-- create new array
      .sort(state.order == 'asc' // <-- select sort comparator
        ? ascendingComparator
        : descendingComparator
      ),
    order: state.order == 'asc' ? 'desc' : 'asc', // <-- toggle order
  }));
}
Related