Search code examples
reactjsreact-hooksrerenderreact-query

avoid Too many re-renders, circular re-rendering


I've got a table, and each row (DifferentialRow) of the table queries for its own data with react-query's useQuery hook. I want the table's rows to be sorted by a given field value (calculated within DifferentialRow), and also only show the top 10.

<Table.Body>
    {filteredVariables!.map((variable, i) => {
      return variable.show ? (
        <DifferentialRow
          key={i}
          ....
          variable={variable.variable}
          setFilteredVariables={setFilteredVariables}
          
        />
      ) : null;
    })}
  </Table.Body>

So when a (DifferentialRow) row has retrieved its data and calculated the sort value, I update the parent filteredVariables object with the new row value, sort, and then set show = true for the top 10 using setFilteredVariables which is passed into DifferentialRow (all shown below).

const diffQuery = useQuery(["differential", {startDate, variable}],fetchDifferentialData);
...
if (diffQuery.isSuccess && diffQuery.data) {
   setSortValue(calcSortValue(diffQuery.data.data));
} 
html rows here

...
function calcSortValue(resultData: any[]) {
    // once we've got a result, and we have calculated the diff we need
    // to set the filteredVariables object that keeps track of cumulative data to only show top x
    try {
      let sortValue = resultData[0].dataValue - resultData[numberOfDays - 1].dataValue;

      setFilteredVariables((prev: { variable: string; diff: number; show: boolean }[]) => {
        let newResults = [...prev, { diff: sortValue, variable, show: undefined }];
        newResults.sort((a, b) => {
          return Math.abs(b.diff || 0) - Math.abs(a.diff || 0);
        });

        let inTopTen = newResults
          .slice(0, 10)
          .map((co) => co.variable)
          .includes(variable);

        let finalResults: CompareObject[];
        if (inTopTen) {
          finalResults = newResults.map((nr) => {
            return nr.variable === variable? { ...nr, show: true }: nr;
          });
        } else {
          finalResults = newResults.map((nr) => {
            return nr.variable === variable? { ...nr, show: false }: nr;
          });
        }
        
        return finalResults;
      });
      return diff;
    } catch (error) {
      return 0;
    }
  }

This is all creating circular re-rendering, and I can't figure out how to get around it.


Solution

  • Okay, so my solution was to completely remove the DistributionRow component and query for the data in the parent (table) component using useQueries (each query representing a row). Then I do all the sorting and slicing on the result from useQueries.

    const diffResults = useQueries(...)
    
    return diffResults.some((dr) => dr.isSuccess) ?  .... <Table.Body>
        {diffResults
          .filter((dr) => dr.isSuccess)
          .map((dr: any) => {
            let dateSorted = dr.data.data.sort(function (a: any, b: any) {
              return new Date(b.runDate).getTime() - new Date(a.runDate).getTime();
            });
            let diff = Math.round(calcDiff(dateSorted) * 10) / 10;
    
            let fullResult = {
              results: dr.data,
              diff,
            };
            return fullResult;
          })
          .sort((a, b) => {
            return Math.abs(b.diff || 0) - Math.abs(a.diff || 0);
          })
          .slice(0, 10)
          .map(({ diff, results }, i) => {
            return (
              <Table.Row key={i} data-testid="table-row">
                <Table.Cell>{results.variable}</Table.Cell>
                {results.data.map((d: any, i: number) =>
                  new Date(d.runDate) > endDate ? (
                    <Table.Cell key={i}>
                      {isNaN(d?.dataValue) ? null : Math.round(d?.dataValue * 10) / 10}
                    </Table.Cell>
                  ) : null
                )}
                {compareColumn ? <Table.Cell>{diff}</Table.Cell> : null}
              </Table.Row>
            );
          })}
      </Table.Body>