Search code examples
javascriptreactjsnext.jsreact-tablereact-table-v7

TypeError: Cannot read properties of undefined (reading 'filter') Tanstack table (React)


I have a tanstack table in NextJS and it works perfectly in dev. However, in prod, it gives a client-side exception:

framework-3b5a00d5d7e8d93b.js:9 TypeError: Cannot read properties of undefined (reading 'filter')
    at 338-40b21a5eb2cada72.js:42:10338
    at Object.getVisibleLeafColumns (338-40b21a5eb2cada72.js:39:249)
    at 338-40b21a5eb2cada72.js:42:5838
    at Object.getHeaderGroups (338-40b21a5eb2cada72.js:39:249)
    at u (index-ef857841339be48b.js:1:1605)
    at ak (framework-3b5a00d5d7e8d93b.js:9:60917)
    at i (framework-3b5a00d5d7e8d93b.js:9:119475)
    at oD (framework-3b5a00d5d7e8d93b.js:9:99114)
    at framework-3b5a00d5d7e8d93b.js:9:98981
    at oO (framework-3b5a00d5d7e8d93b.js:9:98988)
a9 @ framework-3b5a00d5d7e8d93b.js:9
main-f2e125da23ccdc4a.js:1 TypeError: Cannot read properties of undefined (reading 'filter')
    at 338-40b21a5eb2cada72.js:42:10338
    at Object.getVisibleLeafColumns (338-40b21a5eb2cada72.js:39:249)
    at 338-40b21a5eb2cada72.js:42:5838
    at Object.getHeaderGroups (338-40b21a5eb2cada72.js:39:249)
    at u (index-ef857841339be48b.js:1:1605)
    at ak (framework-3b5a00d5d7e8d93b.js:9:60917)
    at i (framework-3b5a00d5d7e8d93b.js:9:119475)
    at oD (framework-3b5a00d5d7e8d93b.js:9:99114)
    at framework-3b5a00d5d7e8d93b.js:9:98981
    at oO (framework-3b5a00d5d7e8d93b.js:9:98988)
$ @ main-f2e125da23ccdc4a.js:1
main-f2e125da23ccdc4a.js:1 A client-side exception has occurred, see here for more info: https://nextjs.org/docs/messages/client-side-exception-occurred

I've looked at my code and examples provided, and I can't see the issue. Here is my table:

import React, { useEffect, useMemo, useState } from 'react'
import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  getPaginationRowModel,
  getSortedRowModel,
  SortingState,
  useReactTable,
} from '@tanstack/react-table'

// Styles
import styles from '../styles/ResultsTable.module.scss'

// Types
import { Row, Table } from '../types/tableTypes'

const ResultsTable = ({items, limit = items.length < 100 ? items.length : 100, totalWords}: Table) => {
  // State
  const [results, setResults] = useState<Row[]>([])
  const [rowsPerPage, setRowsPerPage] = useState<number>(20)
  const [sorting, setSorting] = useState<SortingState>([])
  const [topSize, setTopSize] = useState(limit > items.length ? items.length : limit)
  const [topSizeInput, setTopSizeInput] = useState<number>(limit > items.length ? items.length : limit ?? 0)

  // Regex matcher
  const numMatchRegex = new RegExp(/^[1-9][0-9]*$/g)

  // Functions
  const handleTopSize = ({target: { value }}: React.BaseSyntheticEvent) => {
    // Handle error cases (non-number and larger/smaller than size)
    if (value != '' && !value.match(numMatchRegex)) return

    setTopSizeInput(value)
  }

  const handleTopSizeSubmit = () => {
    // If it's too high or low
    if (topSizeInput > items.length) {
      setTopSizeInput(items.length)
      setTopSize(items.length)
      return
    }
    if (topSizeInput <= 0) {
      setTopSize(1)
      setTopSizeInput(1)
    }

    console.log('not too high or low')

    setTopSizeInput(topSizeInput)
    setTopSize(topSizeInput)
  }

  const columns = useMemo<ColumnDef<Row>[]>(() => [
        {
          id: 'Word',
          accessorFn: info => info[0],
          cell: info => info.getValue(),
          header: () => 'Word'
        },
        {
          id: 'Occurence',
          accessorFn: info => info[1],
          cell: info => info.getValue(),
          header: () => 'Occurence'
        }
  ], [])

  // Table
  const table = useReactTable({
    data: results ?? [],
    columns,
    onSortingChange: setSorting,
    state: {
      sorting
    },
    getCoreRowModel: getCoreRowModel(),
    getPaginationRowModel: getPaginationRowModel(),
    getSortedRowModel: getSortedRowModel(),
  })

  useEffect(() => {
    // Set results to specified limit and slice when topSize changes
    setResults(items.slice(0, topSize <= items.length ? topSize : items.length - 1))
    if (items.length < topSize) setTopSize(items.length)
  }, [items, topSize])
  
  return (
    <div className='slim'>
      <div className={styles.topLine}>
      {!!totalWords && <p data-cy="num-results">Total Words: {totalWords}</p>}
      <p data-cy="top-num-title">Top {topSize} words</p>
      </div>
      {results && (
        <div>
          <table data-cy="results-table">
            <thead>
              {!!table?.getHeaderGroups() && table?.getHeaderGroups()?.map(headerGroup => (
                <tr key={headerGroup.id}>
                  {headerGroup?.headers?.map(header => {
                    return (
                      <th key={header?.id} colSpan={header.colSpan}>
                        {header?.isPlaceholder ? null : (
                          <div
                          { ...{
                            className: header.column.getCanSort() ? 'can-sort' : '',
                            onClick: header.column.getToggleSortingHandler(),
                          }}
                          >
                            {flexRender(
                              header.column.columnDef.header,
                              header?.getContext(),
                            )},
                            {{
                              asc: ' 🔼',
                              desc: ' 🔽',
                              }[header?.column?.getIsSorted() as string] ?? null
                            }
                          </div>
                        )}
                      </th>
                    )
                  })}
                </tr>
              ))}
            </thead>
            <tbody>
              {table
                .getRowModel()
                ?.rows?.slice(0, rowsPerPage)
                ?.map((row, idx) => {
                  return (
                    <tr key={row.id} className={idx % 2 === 0 ? 'left-content' : 'right-content'}>
                      {row.getVisibleCells()?.map(cell => {
                        return (
                          <td key={cell.id}>
                            {flexRender(
                              cell.column.columnDef.cell,
                              cell.getContext(),
                            )}
                          </td>
                        )
                      })
                      }
                    </tr>
                  )
                })
              }
            </tbody>
          </table>
          <div className="h-2" />
            <div className="flex items-center gap-2 table-buttons">
              <button
                className="border rounded p-1"
                onClick={() => table.setPageIndex(0)}
                disabled={!table.getCanPreviousPage()}
              >
                {'<<'}
              </button>
              <button
                className="border rounded p-1"
                onClick={() => table.previousPage()}
                disabled={!table.getCanPreviousPage()}
              >
                {'<'}
              </button>
              <button
                className="border rounded p-1"
                onClick={() => table.nextPage()}
                disabled={!table?.getCanNextPage()}
              >
                {'>'}
              </button>
              <button
                className="border rounded p-1"
                onClick={() => table.setPageIndex(table.getPageCount() - 1)}
                disabled={!table?.getCanNextPage()}
              >
                {'>>'}
              </button>
            </div>
            <div className="flex-row space-between" style={{marginTop: '3rem'}}>
              <div className="flex-column">
                <label>Num. of words to show:</label>
                <input data-cy="top-num-changer" placeholder={topSize} onChange={handleTopSize} value={topSizeInput ?? ''} />
              </div>
              <button data-cy="top-num-submit" style={{justifySelf: 'flex-end'}} onClick={() => setTopSize(handleTopSizeSubmit)}>Change</button>
            </div>
        </div>
        )}
    </div>
  )
  }

export default ResultsTable

Solution

  • It was an issue with Next 13's buggy minifier. Updating to the latest Next 13 will fix the issue.

    The issue that I created (with the intermediary fix if you're on the offending version of Next 13)

    https://github.com/TanStack/table/issues/4610