TanStack / TanStack/table

Custom sortingFn not being run when row key is undefined

オープン
#5,653 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

主要言語
TypeScript
スター
28.4k
フォーク
3.6k
平均マージ
1日 14時間
マージ済み PR(30日)
10

説明

TanStack Table version

8.19.2

Framework/Library version

Vue 3.4.31

Describe the bug and the steps to reproduce it

I was debating whether to even post this issue or not, since it's such a narrow edge case and is mostly inconsequential. But here it is.

My custom sortingFn is was not being run when changing the sort direction of certain columns. I was adding things to the sort func to handle the order of nullish values, and it wasn't having any effect because the function wasn't even being run. Luckily this isn't super important, as the sortUndefined met my needs in this case, but I still think this is a bug.

I can't nail down the exact conditions that cause this, but I think this happens when:

  • there are only 2 rows
  • one of the rows object doesn't have the key that is being sorted by

See the reproducible example. Look at the console logs while clicking on the different columns. The first static column triggers the sorting func. The other two columns do not, unless you add a third row or define the key for the other row.

Reproducible example code for posterity
<template>
  <table>
    <thead>
      <tr v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
        <th
          v-for="header in headerGroup.headers"
          :key="header.id"
          :colSpan="header.colSpan"
          @click="header.column.getToggleSortingHandler()?.($event)"
        >
          <FlexRender
            :render="header.column.columnDef.header"
            :props="header.getContext()"
          />
          <span v-if="header.column.getIsSorted() === 'desc'"> v</span>
          <span v-else-if="header.column.getIsSorted() === 'asc'"> ^</span>
          <span v-else> -</span>
        </th>
      </tr>
    </thead>

    <tbody>
      <tr v-for="row in table.getRowModel().rows" :key="row.id">
        <td v-for="cell in row.getVisibleCells()" :key="cell.id">
          <FlexRender
            :render="cell.column.columnDef.cell"
            :props="cell.getContext()"
          />
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script lang="ts">
type Cell = Record<string, unknown>;

export type Cols<Rows extends Cell[] = Cell[]> = {
  key: Extract<keyof Rows[number], string>;
  name: string;
}[];
</script>

<script setup lang="ts" generic="Rows extends Cell[]">
import { computed } from "vue";
import {
  createColumnHelper,
  FlexRender,
  getCoreRowModel,
  getFacetedMinMaxValues,
  getFacetedRowModel,
  getFacetedUniqueValues,
  getFilteredRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  useVueTable,
  type Row as TanstackRow,
} from "@tanstack/vue-table";
import type { SortingFn } from "@tanstack/vue-table";

type Props = {
  cols: Cols<Rows>;
  rows: Rows;
};

const props = defineProps<Props>();

type Row = Rows[number];

const columnHelper = createColumnHelper<Row>();

const sortingFunction: SortingFn<Row> = (
  a: TanstackRow<Row>,
  b: TanstackRow<Row>,
) => {
  console.log("sort");
  if (a < b) return -1;
  if (a > b) return 1;
  return 0;
};

const columns = computed(() =>
  props.cols.map((col) =>
    columnHelper.accessor((row: Row) => row[col.key], {
      id: col.key,
      header: col.name,
      enableSorting: true,
      sortingFn: sortingFunction,
    }),
  ),
);

const table = useVueTable({
  get data() {
    return props.rows;
  },
  get columns() {
    return columns.value;
  },
  getCoreRowModel: getCoreRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  getSortedRowModel: getSortedRowModel(),
  getFacetedRowModel: getFacetedRowModel(),
  getFacetedUniqueValues: getFacetedUniqueValues(),
  getFacetedMinMaxValues: getFacetedMinMaxValues(),
  initialState: {
    pagination: {
      pageIndex: 0,
      pageSize: 999999,
    },
  },
});
</script>
<script setup lang="ts">
import AppTable from "./components/AppTable.vue";

const rows = [
  { static: "test", abc: 123 },
  { static: "test", def: 456 },
  // { static: "test", abc: 789 },
];

const cols = [
  { key: "static", name: "Static" },
  { key: "abc", name: "ABC" },
  { key: "def", name: "DEF" },
];
</script>

<template>
  <AppTable :rows="rows" :cols="cols" />
</template>
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)

https://codesandbox.io/p/devbox/tanstack-table-sorting-bug-782h9d

Do you intend to try to help solve this bug with your own PR?

No, because I do not know how

Terms & Code of Conduct
  • I agree to follow this project's Code of Conduct
  • I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.

sortUndefined met my needs here, but I was also able to force the sortingFn to run by making sure every row being returned from get data() { return props.rows; } has every col key defined:

const _rows = computed(() => {
  /** row with all possible object keys at least defined */
  const blankRow = Object.fromEntries(props.cols.map((col) => [col.key, ""]));

  return props.rows.map((row) => ({
    /** start with blank row */
    ...cloneDeep(blankRow), // lodash cloneDeep
    /** add actual row data */
    ...row,
  }));
});

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

リンクされているCodeSandboxの再現とgetSortedRowModelのソートパスから始め、行でアクセスされるキーが省略されている列と静的な列を比較します。これらの列のソート方向を変更したときにカスタムsortingFnが呼び出され、undefined-keyを持つ2行のケースがカバレッジに含まれていれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
typescript
領域
frontend
issue の種類
バグ
難易度
3/5
見積もり時間
1〜2日
活発さ
停滞
明瞭さ
おおむね明確
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。