Home > Software design >  React updates after two clicks instead of one
React updates after two clicks instead of one

Time:12-31

I'm creating a sortable table, and my issue is that the sorting shows after TWO clicks on a column, instead of just after one click.

This is my sorting logic: (DATA is hardcoded)

export const useFetch = (order) => {
  const [data, setData] = useState();
  const orderBy = order?.by || 'offer_id';
  const fromTo = order?.fromTo || SORTING_ORDER.ascending;

  useEffect(() => {
    if (!orderBy || !fromTo) return;

    let orderedData = DATA.sort((a, b) => (a[orderBy] - b[orderBy]));
    setData(orderedData);

  }, [orderBy, fromTo]);

  return { data, status };
};

And I'm using this hook like this, from the component that has that table.

export const AcceptedOffers = ({ setModalIsOpen }) => {
  const [order, setOrder] = useState({ by: 'maturity', fromTo: SORTING_ORDER.ascending });
  const { data, status } = useFetch(order);

  function onHeaderClick(header) {
    setOrder({ by: header, fromTo: SORTING_ORDER.descending });
  }


  return (
    <WidgetContainer>
      <Title>
        Accepted Offers
      </Title>
      <Table>
        <Header>
          <tr>
            {
              Object.entries(HEADERS).map(([key, value]) =>
                <th
                  key={key}
                  onClick={() => onHeaderClick(key)}
                >
                  {value}
                </th>)
            }
          </tr>
        </Header>
        <Body>
          {
            data?.map(row => (<tr key={row.offer_id}>
              <td>{row.offer_id}</td>
etc...

Can anyone explain what's wrong with this. Thank you.

CodePudding user response:

The side effect represented by your useEffectis executed after the render triggered by the click: the data are rendered first, then sorted. That's where your "delay" commes from.

Here is an other solution. It may suit you, or not: the purpose is to show an alternative implementation to trigger the sort when order is modified, but without useEffect. It works by "overloading" setOrder:

export const useFetch = (initialOrder) => {
  // useReducer may be a better choice here,
  // to store order and data with a single state
  // (and update this state through a single call)
  const [order, setOrder] = useState(initialOrder);
  const [data, setData] = useState();

  const publicSetOrder = (newOrder) => {
    setOrder(newOrder);

    const orderBy = newOrder?.by || 'offer_id';
    const fromTo = newOrder?.fromTo || SORTING_ORDER.ascending;

    if (!orderBy || !fromTo) return;

    let orderedData = DATA.sort((a, b) => (a[orderBy] - b[orderBy]));
    setData(orderedData);

  };

  return { order, setOrder: publicSetOrder, data, status };
};
export const AcceptedOffers = ({ setModalIsOpen }) => {
  const { order, setOrder, data, status } = useFetch({ by: 'maturity', fromTo: SORTING_ORDER.ascending });

  function onHeaderClick(header) {
    setOrder({ by: header, fromTo: SORTING_ORDER.descending });
  }

  // ...

Feel free to adapt to your use case ;)

  • Related