shadcn: How to filter by apps name?

31 Views Asked by At

I am building a simple budget tracker, and I'd like to let user to filter by apps name.

Below is my code.

[
  {
    "status": "on",
    "id": "NZMwD83Nxg",
    "name": "GTA_UA_UAC_Android_01_DAU",
    "dailyBudget": 800,
    "apps": [
      {
        "name": "app 1",
        "icon": "https://marketing-0-7-0-85-220.png/230x0w.webp"
      },
      {
        "name": "app 2",
        "icon": "https://marketing-0-7-0-99-220.png/230x0w.webp"
      }
    ],
    "currency": "USD",
  },
  {
    "status": "on",
    "id": "aXly0x0vX6",
    "name": "GTA_UA_UAC_Android_02_DAU",
    "dailyBudget": 65,
    "apps": [
      {
        "name": "app 3",
        "icon": "https://marketing-0-7-0-85-220.png/230x0w.webp"
      }
    ],
    "currency": "USD",
  }
]

And this is my column type.

type AppsType = {
  name: string;
  icon: string;
};

export type MainCampaign = {
  id: string;
  name: string;
  status: string;
  apps: AppsType[];
  dailyBudget: number;
  currency: string;
};

export const columns: ColumnDef<MainCampaign>[] = [
  {
    accessorKey: "icon",
    header: ({ column }) => (
      <Button
        variant="ghost"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        Apps
        <ArrowUpDown className="ml-2 h-4 w-4" />
      </Button>
    ),
    cell: ({ row }) => {
      const apps: AppsType[] = row.getValue("apps");

      return (
        <div style={{ display: "flex", gap: "10px" }}>
          {apps.map((app, index) => (
            <Avatar key={index}>
              <AvatarImage src={app.icon} alt={app.name} />
              <AvatarFallback>{app.name}</AvatarFallback>
            </Avatar>
          ))}
        </div>
      );
    },
  },
  {
    accessorKey: "apps",
    header: ({ column }) => (
      <Button
        variant="ghost"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        App Names
        <ArrowUpDown className="ml-2 h-4 w-4" />
      </Button>
    ),
    cell: ({ row }) => {
      const apps: AppsType[] = row.getValue("apps");
      return apps.map((app, index) => <div key={index}>{app.name}</div>);
    },
  },
{
    accessorKey: "name",
    header: ({ column }) => {
      return (
        <Button
          variant="ghost"
          onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
        >
          Campaign Name
          <ArrowUpDown className="ml-2 h-4 w-4" />
        </Button>
      );
    },
  },
  ...
]

This is my Input inside data-table for now, and it's worked when using apps, no result after typing.

<div className="flex items-center py-4">
  <Input
     id="filter-app"
     placeholder="Filter App..."
     value={table.getColumn("apps")?.getFilterValue()?.toString() ?? ""}
     onChange={(event) =>
     table.getColumn("apps")?.setFilterValue(event.target.value)
      }
     className="max-w-sm"
  />
</div>

I have logged the app names, and the result looks like this: ["app1", "app2"], ["app3"]. My initial thought was to check if they include the filter value, then return the string, but I got stuck when implementing the function.

Would really appreciate your guidance.

1

There are 1 best solutions below

0
Andrea Fan On

I have solved the problem, below is my implementation.

 {
    accessorKey: "apps",
    header: ({ column }) => (
      <Button
        variant="ghost"
        onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
      >
        App Names
        <ArrowUpDown className="ml-2 h-4 w-4" />
      </Button>
    ),
    cell: ({ row }) => {
      const apps: AppsType[] = row.getValue("apps");
      return apps.map((app, index) => <div key={index}>{app.name}</div>);
    },
    filterFn: (
      row: Row<MainCampaign>,
      _cloumnId: string,
      filterValue: string
    ) => {
      const apps: AppsType[] = row.getValue("apps");
      return apps.some((app) =>
        app.name.toLowerCase().includes(filterValue.toLowerCase())
      );
    },
  },

And then this is my data-table

 const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    onSortingChange: setSorting,
    getSortedRowModel: getSortedRowModel(),
    onColumnFiltersChange: setColumnFilters,
    getFilteredRowModel: getFilteredRowModel(),
    onPaginationChange: setPagination,
    filterFns: {
      apps: (row, _columnId, value) => {
        const apps: AppsType[] = row.getValue("apps");
        const appsNameArr: string[] = apps.map((app) => app.name.toLowerCase());
        return appsNameArr.some((name) => name.toLowerCase().includes(value));
      },
    },
    state: {
      sorting,
      columnFilters,
      pagination,
    },
  });

I did a bit of cleanup, extracted my filter function in util.ts

so it would be like this.

  {
    accessorKey: "apps",
    header: ({ column }) => (
      ...
    ),
    cell: ({ row }) => {
     ...
    },
    filterFn: customFilterFn,
  },
// data-table.tsx

const table = useReactTable({
    data,
    ...
    filterFns: {
      apps: customFilterFn,
    },
    state: {
      ...
    },
  });