'React-Table with typescript filtering outside of table component
Hello I am currently working on a project with React-table and typescript however I cant seem to make the custom filter work, the custom filter should work outside the table, I've added types that I have found, but can't seem to make it work, I keep getting cannot read foreach of undefined even though I have the data
enter image description here enter image description here
table Component
/* eslint-disable react/no-array-index-key */
import Image from 'next/image';
import { Text } from '@/ui';
import { UserActivities } from '@/types/user-types';
import { useEffect, useMemo, useState } from 'react';
import { useTable, useFilters } from 'react-table';
import Link from 'next/link';
import { DropDown, Icon } from '..';
import TableFilter from '../dropdown/table-filter';
interface Props {
title: string;
activities?: UserActivities;
}
const DropDownOneOptions = [
{
label: 'All',
value: 'ALL',
},
{
label: 'Last 24 hours',
value: 'Last 24 hours',
},
{
label: 'Last 7 Days',
value: 'Last 7 Days',
},
{
label: 'Last 30 Days',
value: 'Last 30 Days',
},
{
label: 'Last 90 Days',
value: 'Last 90 Days',
},
];
// TODO his needs to be refactored extremely bad to do it like this
const customFilterFunction = (rows: any, id: any, filterValue: any) => {
console.log({ rows, id, filterValue });
if (filterValue === 'ALL') {
return rows;
}
if (filterValue === 'Last 24 hours') {
return rows.filter((row) => {
const date = new Date(row.original.date);
const currentDate = new Date();
const diffTime = Math.abs(currentDate.getTime() - date.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays <= 1;
});
}
if (filterValue === 'Last 7 Days') {
return rows.filter((row) => {
const date = new Date(row.original.date);
const currentDate = new Date();
const diffTime = Math.abs(currentDate.getTime() - date.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays <= 7;
});
}
if (filterValue === 'Last 30 Days') {
return rows.filter((row) => {
const date = new Date(row.original.date);
const currentDate = new Date();
const diffTime = Math.abs(currentDate.getTime() - date.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays <= 30;
});
}
if (filterValue === 'Last 90 Days') {
return rows.filter((row) => {
const date = new Date(row.original.date);
const currentDate = new Date();
const diffTime = Math.abs(currentDate.getTime() - date.getTime());
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays <= 90;
});
}
};
const UserTable = ({ title, activities }: Props) => {
const [filterDays, setFilterDays] = useState(DropDownOneOptions[0].label);
const data = useMemo(
// check if the activities is empty
() =>
activities
? activities.activities?.map((activity) => ({
...activity,
}))
: [],
[activities]
);
const columns = useMemo(
(): any => [
{
Header: 'Event',
accessor: 'event',
// filters
},
{
Header: 'Item',
accessor: 'item',
Cell: ({ cell: { value } }: any) => (
<div className="flex gap-[12px]">
<div className="w-[45px] h-[45px] rounded-[10px]">
<Link href={`/${value?.token_id}?${value?.contract}`}>
<a>
<Image
src={value ? value.image_path : 'https://i.pravatar.cc/300'}
width={45}
height={45}
className="rounded-[10px]"
/>
</a>
</Link>
</div>
<div>
<Text>{value.nickname || 'Very Smart'}</Text>
<Text size="sm" variant="primary">
{' '}
{value.token_id || 2}
</Text>
</div>
</div>
),
},
{
Header: 'Price',
accessor: 'price',
Cell: ({ cell: { value } }: any) =>
value ? (
<div className="flex items-center ">
<Icon src="/eths.svg" width={15} height={16} className="mr-1" />
<Text className="text-lg font-bold">{value}</Text>
</div>
) : null,
},
// from is an object that contains username and owner_address
{
Header: 'From',
accessor: 'from',
Cell: ({ cell: { value } }: any) => (
<Link href={`/homepage/${value.owner_address}`}>
<a>
<Text size="sm" variant="accent">
{value.username || 'from un is here'}
</Text>
</a>
</Link>
),
},
// to is an object that contains username and owner_address
{
Header: 'To',
accessor: 'to',
Cell: ({ cell: { value } }: any) => (
<Link href={`/homepage/${value.owner_address}`}>
<a>
<Text size="sm" variant="accent">
{value.username || 'from un is here'}
</Text>
</a>
</Link>
),
},
{
Header: 'Date',
accessor: 'date_tag',
Cell: ({ cell: { value } }: any) => <Text size="sm">{value}</Text>,
filter: customFilterFunction,
},
],
[]
);
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow, setFilter } =
useTable({ columns, data }, useFilters);
// console.log(setFilter());
useEffect(() => {
if (activities) {
setFilter('date_tag', filterDays);
}
}, [filterDays]);
return (
<div className="flex flex-col mt-[25px] bg-[#1E1E1E] p-[50px] rounded-m-round">
<div className="justify-between items-center grid grid-cols-3">
<Text variant="accent" size="3xl" className="col-span-2">
{title}
</Text>
{/* */}
<TableFilter
options={DropDownOneOptions}
filterDay={filterDays}
setFilterDays={setFilterDays}
/>
</div>
{/* table Starts here */}
<div className="w-full h-[1050px] overflow-y-auto no-scrollbar gap-5">
<table className="w-full mt-12 border-collapse">
<thead>
{headerGroups.map((headerGroup, i) => {
const { key, ...restHeaderGroupProps } = headerGroup.getHeaderGroupProps();
return (
<tr key={key} {...restHeaderGroupProps}>
{headerGroup.headers.map((column, i) => {
const { key, ...restColumn } = column.getHeaderProps();
return (
<th
key={key}
{...restColumn}
className="text-lg text-white/50 text-left pr-4"
>
{column.render('Header')}
</th>
);
})}
</tr>
);
})}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
const { key, ...restRowProps } = row.getRowProps();
return (
<tr key={key} {...restRowProps}>
{row.cells.map((cell) => {
const { key, ...restCellProps } = cell.getCellProps();
return (
<td
key={key}
{...restCellProps}
className="pt-[30px] pb-5 whitespace-nowrap text-sm text-white text-left capitalize"
>
{cell.render('Cell')}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
};
export default UserTable;
filter component
import { Listbox } from '@headlessui/react';
import clsx from 'clsx';
import { Fragment, useState } from 'react';
interface Props {
options: {
label: string;
value: string;
}[];
filterDay: any;
setFilterDays: any;
}
const TableFilter = ({ options, filterDay, setFilterDays }: Props) => {
// const [selectedItem, setSelectedItem] = useState('this is');
const handleChange = (e: any) => {
setFilterDays(e.value);
};
return (
<Listbox
as="div"
value={filterDay}
onChange={handleChange}
className="relative inline-block text-left col-start-4 z-10"
>
<Listbox.Button
className="rounded-[10px] flex justify-between border text-white border-white/60 shadow-sm px-[15px] py-[18px] focus:outline-none focus:ring-1 focus:ring-offset-1 focus:ring-offset-gray-100 focus:ring-mgreen hover:border-mgreen w-[223px] max-w-[223px]
"
>
{filterDay}
<svg
className="mr-1 ml-2 h-5 w-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M19 9l-7 7-7-7"
/>
</svg>
</Listbox.Button>
<Listbox.Options className="origin-top-right absolute right-0 mt-[15px] w-56 shadow-lg bg-black ring-1 ring-black ring-opacity-5 focus:outline-none rounded-[10px] ">
<div className="py-1">
{options.map((option) => (
<Listbox.Option key={option.label} value={option} as={Fragment}>
{({ active, selected }) => (
<li
className={clsx(
active ? 'bg-mgreen text-black' : 'text-white text-sm',
'block px-4 py-2 text-sm'
)}
>
{option.label}
</li>
)}
</Listbox.Option>
))}
</div>
</Listbox.Options>
</Listbox>
);
};
export default TableFilter;
types I have used in global
/* eslint-disable no-use-before-define */
// Type definitions for react-table 7
// Project: https://github.com/tannerlinsley/react-table#readme
// Definitions by: Adrien Denat <https://github.com/grsmto>
// Artem Berdyshev <https://github.com/berdyshev>
// Christian Murphy <https://github.com/ChristianMurphy>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.0
import { Dispatch, SetStateAction, ReactNode } from 'react';
import {
TableInstance,
UseColumnOrderInstanceProps,
UseColumnOrderState,
UseExpandedHooks,
UseExpandedInstanceProps,
UseExpandedOptions,
UseExpandedRowProps,
UseExpandedState,
UseFiltersColumnOptions,
UseFiltersColumnProps,
UseFiltersInstanceProps,
UseFiltersOptions,
UseFiltersState,
UseGlobalFiltersInstanceProps,
UseGlobalFiltersOptions,
UseGlobalFiltersState,
UseGroupByCellProps,
UseGroupByColumnOptions,
UseGroupByColumnProps,
UseGroupByHooks,
UseGroupByInstanceProps,
UseGroupByOptions,
UseGroupByRowProps,
UseGroupByState,
UsePaginationInstanceProps,
UsePaginationOptions,
UsePaginationState,
UseResizeColumnsColumnOptions,
UseResizeColumnsColumnProps,
UseResizeColumnsOptions,
UseResizeColumnsState,
UseRowSelectHooks,
UseRowSelectInstanceProps,
UseRowSelectOptions,
UseRowSelectRowProps,
UseRowSelectState,
UseSortByColumnOptions,
UseSortByColumnProps,
UseSortByHooks,
UseSortByInstanceProps,
UseSortByOptions,
UseSortByState,
} from 'react-table';
declare module 'react-table' {
export interface Cell<D> {
render: (type: string) => any;
getCellProps: () => any;
row: Row<D>;
column: Column<D>;
state: any;
value: any;
}
export interface Row<D> {
index: number;
cells: Cell<D>[];
getRowProps: () => any;
original: any;
}
export interface HeaderColumn<D, A extends keyof D = never> {
/**
* This string/function is used to build the data model for your column.
*/
accessor: A | ((originalRow: D) => string);
Header?: string | ((props: TableInstance<D>) => ReactNode);
Filter?: string | ((props: TableInstance<D>) => ReactNode);
Cell?: string | ((cell: Cell<D>) => ReactNode);
/**
* This is the unique ID for the column. It is used by reference in things like sorting, grouping, filtering etc.
*/
id?: string | number;
minWidth?: string | number;
maxWidth?: string | number;
width?: string | number;
canSortBy?: boolean;
sortByFn?: (a: any, b: any, desc: boolean) => 0 | 1 | -1;
defaultSortDesc?: boolean;
}
export interface Column<D, A extends keyof D = never> extends HeaderColumn<D, A> {
id: string | number;
}
export type Page<D> = Row<D>[];
export interface EnhancedColumn<D, A extends keyof D = never> extends Column<D, A> {
render: (type: string) => any;
getHeaderProps: (userProps?: any) => any;
getSortByToggleProps: (userProps?: any) => any;
sorted: boolean;
sortedDesc: boolean;
sortedIndex: number;
}
export type HeaderGroup<D, A extends keyof D = never> = {
headers: EnhancedColumn<D, A>[];
getRowProps: (userProps?: any) => any;
};
export interface Hooks<D> {
beforeRender: [];
columns: [];
headerGroups: [];
headers: [];
rows: Row<D>[];
row: [];
renderableRows: [];
getTableProps: [];
getRowProps: [];
getHeaderRowProps: [];
getHeaderProps: [];
getCellProps: [];
}
export interface TableInstance<D>
extends TableOptions<D>,
UseRowsValues<D>,
UseFiltersValues,
UsePaginationValues<D>,
UseColumnsValues<D> {
hooks: Hooks<D>;
rows: Row<D>[];
columns: EnhancedColumn<D>[];
getTableProps: (userProps?: any) => any;
getRowProps: (userProps?: any) => any;
prepareRow: (row: Row<D>) => any;
getSelectRowToggleProps: (userProps?: any) => any;
toggleSelectAll: (forcedState: boolean) => any;
}
export interface TableOptions<D> {
data: D[];
columns: HeaderColumn<D>[];
state?: [any, Dispatch<SetStateAction<any>>];
debug?: boolean;
sortByFn?: (a: any, b: any, desc: boolean) => 0 | 1 | -1;
manualSorting?: boolean;
disableSorting?: boolean;
defaultSortDesc?: boolean;
disableMultiSort?: boolean;
}
export interface RowsProps {
subRowsKey: string;
}
export interface FiltersProps {
filterFn: () => void;
manualFilters: boolean;
disableFilters: boolean;
setFilter: (any, any) => any;
setAllFilters: () => any;
}
export interface UsePaginationValues<D> {
nextPage: () => any;
previousPage: () => any;
setPageSize: (size: number) => any;
gotoPage: (page: number) => any;
canPreviousPage: boolean;
canNextPage: boolean;
page: Page<D>;
pageOptions: [];
}
export interface UseRowsValues<D> {
rows: Row<D>[];
}
export interface UseColumnsValues<D> {
columns: EnhancedColumn<D>[];
headerGroups: HeaderGroup<D>[];
headers: EnhancedColumn<D>[];
}
export interface UseFiltersValues {
setFilter: (any, any) => any;
setAllFilters: () => any;
}
export function useTable<D>(
props: TableOptions<D>,
...plugins: any[]
): TableInstance<D>;
export function useColumns<D>(
props: TableOptions<D>
): TableOptions<D> & UseColumnsValues<D>;
export function useRows<D>(props: TableOptions<D>): TableOptions<D> & UseRowsValues<D>;
export function useFilters<D>(props: TableOptions<D>): TableOptions<D> & {
rows: Row<D>[];
};
export function useSortBy<D>(props: TableOptions<D>): TableOptions<D> & {
rows: Row<D>[];
};
export function useGroupBy<D>(
props: TableOptions<D>
): TableOptions<D> & { rows: Row<D>[] };
export function usePagination<D>(props: TableOptions<D>): UsePaginationValues<D>;
export function useFlexLayout<D>(props: TableOptions<D>): TableOptions<D>;
export function useExpanded<D>(props: TableOptions<D>): TableOptions<D> & {
toggleExpandedByPath: () => any;
expandedDepth: [];
rows: [];
};
export function useTableState(
initialState?: any,
overriddenState?: any,
options?: {
reducer?: (oldState: any, newState: any, type: string) => any;
useState?: [any, Dispatch<SetStateAction<any>>];
}
): any;
export const actions: any;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
