Search code examples
reactjstypescriptreact-nativereact-hooksreact-context

When sorting components state of children are not updated


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

Solution

  • 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
      }));
    }