Skip to content

[BUG] useMany hook causes useTable filters lost. #7111

@weichengwu

Description

@weichengwu

Describe the bug

Please take a look at my code and screenshot.

Steps To Reproduce

Code

import { useMany } from '@refinedev/core';
import { useTable } from '@refinedev/react-table';
import { createColumnHelper } from '@tanstack/react-table';
import React from 'react';

import type { IotCategory, IotProduct } from '@/api/foundation';
import TableFilter from '@/components/iot-ui/table/table-filter';
import { DataTable } from '@/components/refine-ui/data-table/data-table';
import { DataTableActionsDropdown } from '@/components/refine-ui/data-table/data-table-actions-dropdown';
import {
  ListView,
  ListViewHeader,
} from '@/components/refine-ui/views/list-view';
import { Badge } from '@/components/ui/badge';
import { FOUNDATION_PROVIDER_NAME } from '@/resources.tsx';

const nodeTypeLabels: Record<string, string> = {
  gateway: '网关设备',
  gateway_subset: '网关子设备',
  direct: '直连设备',
};

const renderNodeType = (nodeType?: string) => {
  if (!nodeType) return <Badge variant='outline'>未知</Badge>;
  const label = nodeTypeLabels[nodeType] ?? nodeType;
  return <Badge variant='outline'>{label}</Badge>;
};

const renderEnable = (enable?: number) => {
  if (enable === 1) {
    return <Badge className='bg-green-500 text-white'>已启用</Badge>;
  }
  if (enable === 0) {
    return <Badge className='bg-gray-500 text-white'>未启用</Badge>;
  }
  return <Badge variant='outline'>未知</Badge>;
};

export const IotProductsList = () => {
  const [categoryNameById, setCategoryNameById] = React.useState<
    Record<number, string>
  >({});
  const [isFetchingCategories, setIsFetchingCategories] = React.useState(false);

  const columns = React.useMemo(() => {
    const columnHelper = createColumnHelper<IotProduct>();

    return [
      columnHelper.accessor('id', {
        id: 'id',
        header: 'ID',
        size: 60,
      }),
      columnHelper.accessor('name', {
        id: 'name',
        header: '产品名称',
      }),
      columnHelper.accessor('pid', {
        id: 'pid',
        header: '产品标识',
      }),
      columnHelper.accessor('nodeType', {
        id: 'nodeType',
        header: '节点类型',
        cell: (info) => renderNodeType(info.getValue()),
      }),
      columnHelper.accessor('categoryId', {
        id: 'categoryId',
        header: '所属分类',
        cell: (info) => {
          const categoryId = info.getValue();
          if (categoryId === undefined || categoryId === null) {
            return '-';
          }
          const name = categoryNameById[categoryId];
          if (name) {
            return name;
          }
          if (isFetchingCategories) {
            return '加载中...';
          }
          return '未找到';
        },
        size: 160,
      }),
      columnHelper.accessor('enable', {
        id: 'enable',
        header: '启用',
        cell: (info) => renderEnable(info.getValue()),
      }),
      columnHelper.display({
        id: 'actions',
        header: '操作',
        cell: ({ row }) => (
          <DataTableActionsDropdown recordItemId={row.original.id} />
        ),
      }),
    ];
  }, [categoryNameById, isFetchingCategories]);

  const table = useTable({
    columns,
    refineCoreProps: {
      syncWithLocation: true,
    },
    initialState: {
      columnPinning: {
        left: [],
        right: ['actions'], // Pin actions column to the right
      },
    },
  });

  const products =
    (table.refineCore.tableQuery.data?.data as IotProduct[] | undefined) ?? [];

  const categoryIds = React.useMemo(() => {
    const ids = products
      .map((product) => product.categoryId)
      .filter((id): id is number => id !== undefined && id !== null);
    return Array.from(new Set(ids));
  }, [products]);

  const categoriesQuery = useMany<IotCategory>({
    resource: 'iotCategory',
    ids: categoryIds,
    dataProviderName: FOUNDATION_PROVIDER_NAME,
    queryOptions: {
      enabled: categoryIds.length > 0,
    },
  });

  const categoryRecords = React.useMemo(
    () =>
      extractRecords<IotCategory>(
        categoriesQuery.query.data ?? categoriesQuery.result.data,
      ),
    [categoriesQuery.query.data, categoriesQuery.result.data],
  );

  React.useEffect(() => {
    setIsFetchingCategories(categoriesQuery.query.isFetching);
  }, [categoriesQuery.query.isFetching]);

  React.useEffect(() => {
    if (!categoryRecords.length) {
      return;
    }
    setCategoryNameById((prev) => {
      let changed = false;
      const next = { ...prev };
      for (const item of categoryRecords) {
        if (item?.id === undefined || item.id === null) continue;
        if (!item.name) continue;
        if (next[item.id] !== item.name) {
          next[item.id] = item.name;
          changed = true;
        }
      }
      return changed ? next : prev;
    });
  }, [categoryRecords]);

  return (
    <ListView>
      <ListViewHeader canCreate />
      <TableFilter
        table={table}
        filters={[
          {
            label: '产品名称',
            field: 'name',
            type: 'input',
          },
          {
            label: '所属分类',
            field: 'categoryId',
            type: 'select',
            resource: 'iotCategory',
            resourceOptions: {
              label: 'name',
              value: 'id',
            },
          },
        ]}
      />
      <DataTable table={table} />
    </ListView>
  );
};

export default IotProductsList;

function extractRecords<T>(response: unknown): T[] {
  if (!response) return [];
  if (Array.isArray(response)) {
    return response as T[];
  }
  if (
    typeof response === 'object' &&
    response !== null &&
    Array.isArray((response as { data?: unknown }).data)
  ) {
    return ((response as { data: unknown[] }).data ?? []) as T[];
  }
  if (
    typeof response === 'object' &&
    response !== null &&
    Array.isArray((response as { data?: { data?: unknown[] } }).data?.data)
  ) {
    return ((response as { data: { data: unknown[] } }).data?.data ??
      []) as T[];
  }
  if (
    typeof response === 'object' &&
    response !== null &&
    Array.isArray(
      (response as { data?: { records?: unknown[] } }).data?.records,
    )
  ) {
    return ((response as { data: { records: unknown[] } }).data?.records ??
      []) as T[];
  }
  return [];
}

Screenshot

Image

Expected behavior

...

Packages

System:

  • OS: macOS 26.1
  • CPU: (14) arm64 Apple M4 Pro

Binaries:

  • Node: 23.11.1 - /Users/waynewu/.local/state/fnm_multishells/60926_1762825045540/bin/node
  • Yarn: Not Found
  • npm: 11.6.0 - /opt/homebrew/bin/npm

Browsers:

  • Chrome: 142.0.7444.135
  • Firefox: Not Found
  • Safari: 26.1

Refine Packages:

  • @refinedev/cli: 2.16.50
  • @refinedev/core: 5.0.5
  • @refinedev/devtools: 2.0.3
  • @refinedev/kbar: 2.0.1
  • @refinedev/react-hook-form: 5.0.2
  • @refinedev/react-router: 2.0.3
  • @refinedev/react-table: 6.0.1
  • @refinedev/simple-rest: 6.0.1

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions