Problems getting native react tables to render correctly

Good evening, Plone community!

I am in the process of rewriting my code for a Volto view so that it uses non-jQuery/datatables.net content. Unfortunately, I ran into an error saying "Cannot read property 'forEach' of undefined". I am currently using react-tables yarn package.

Here is some of the code:

const SearchYCDocsRT = () => {
  const columns = useMemo(
    () => [
      { Header: '#', accessor: 'number' },
      { Header: 'Incoming Seek', accessor: 'incomingseek' },
      { Header: 'Created', accessor: 'created' },
      { Header: 'empID', accessor: 'empid' },
      { Header: 'First Name', accessor: 'firstname' },
      { Header: 'Last Name', accessor: 'lastname' },
      { Header: 'Documents', accessor: 'documents' },
      {
        Header: 'State',
        accessor: 'state',
        Filter: SelectColumnFilter,
        filter: 'includes',
      },
      { Header: 'Action', accessor: 'action' },
    ],
    [],
  );

  const data = React.useMemo(() => {}, []);

  return (
    <Styles>
      <ReactTable columns={columns} data={data} />
    </Styles>
  );
};

export default SearchYCDocsRT;

I am currently trying to use the filtering and pagination options in react-tables for this project but with no success. What else needs to be done here?

I thank you kindly.

Sincerely,

rbrown12

Hi @rbrown12 , its a bit tricky to tell this without seeing the code and most importantly the traceback. At the first glance, it can be something with your React Table code.

Thanks!

Here is some more of the code below. I was unable to display all of it at once due to the size limit.

const ReactTable = ({ columns, data }) => {
  const results = useSelector(
    (state) => state.search.subrequests.content?.items,
  );
  const dispatch = useDispatch();
  useEffect(() => {
    dispatch(
      searchContent(
        '/',
        {
          meta_type: 'Dexterity Item',
          fullobjects: true,
        },
        'content',
      ),
    );
  }, [dispatch]);
  const filterTypes = React.useMemo(
    () => ({
      // Add a new fuzzyTextFilterFn filter type.
      fuzzyText: fuzzyTextFilterFn,
      // Or, override the default text filter to use
      // "startWith"
      text: (rows, id, filterValue) => {
        return rows.filter((row) => {
          const rowValue = row.values[id];
          return rowValue !== undefined
            ? String(rowValue)
                .toLowerCase()
                .startsWith(String(filterValue).toLowerCase())
            : true;
        });
      },
    }),
    [],
  );

  const defaultColumn = React.useMemo(
    () => ({
      // Let's set up our default Filter UI
      Filter: DefaultColumnFilter,
    }),
    [],
  );

  const {
    getTableProps,
    // getTableBodyProps,
    headerGroups,
    rows,
    prepareRow,
    state,
    visibleColumns,
    preGlobalFilteredRows,
    setGlobalFilter,
  } = useTable(
    {
      columns,
      data,
      defaultColumn, // Be sure to pass the defaultColumn option
      filterTypes,
    },
    useFilters, // useFilters!
    useGlobalFilter, // useGlobalFilter!
  );

  // We don't want to render all of the rows for this example, so cap
  // it for this use case
  const firstPageRows = rows.slice(0, 10);

  return (
    <Container>
      <Table {...getTableProps()} celled striped sortable className="listing">
        <Table.Header>
          {headerGroups.map((headerGroup) => (
            <Table.Row {...headerGroup.getHeaderGroupProps()}>
              {headerGroup.headers.map((column) => (
                <Table.HeaderCell {...column.getHeaderProps()}>
                  {column.render('Header')}
                </Table.HeaderCell>
              ))}
            </Table.Row>
          ))}
        </Table.Header>
        <Table.Body>...</Table.Body>
      </Table>
      <br />
      <div>Showing the first 25 results of {rows.length} rows</div>
      <div>
        <pre>
          <code>{JSON.stringify(state.filters, null, 2)}</code>
        </pre>
      </div>
    </Container>
  );
};
import React, { useMemo, useEffect } from 'react';
import { Container, Modal, Input, Button, Table } from 'semantic-ui-react';
import styled from 'styled-components';
import {
  useTable,
  useFilters,
  useGlobalFilter,
  useAsyncDebounce,
} from 'react-table';
// A great library for fuzzy filtering/sorting items
import matchSorter from 'match-sorter';
import moment from 'moment';
import { Link } from 'react-router-dom';
import PDFViews from '../../PDFViews/PDFViews';
import ContentStatusHistory from '../../ContentStatusHistory/ContentStatusHistory';
import History from '../../History/History';
import { useSelector, useDispatch } from 'react-redux';
import { searchContent } from '../../../actions/search/search';

const Styles = styled.div`
  padding: 1rem;
  table {
    border-spacing: 0;
    border: 1px solid black;
    tr {
      :last-child {
        td {
          border-bottom: 0;
        }
      }
    }
    th,
    td {
      margin: 0;
      padding: 0.5rem;
      border-bottom: 1px solid black;
      border-right: 1px solid black;
      :last-child {
        border-right: 0;
      }
    }
  }
`;

// Define a default UI for filtering
const GlobalFilter = ({
  preGlobalFilteredRows,
  globalFilter,
  setGlobalFilter,
}) => {
  const count = preGlobalFilteredRows.length;
  const [value, setValue] = React.useState(globalFilter);
  const onChange = useAsyncDebounce((value) => {
    setGlobalFilter(value || undefined);
  }, 200);

  return (
    <span>
      Search:{' '}
      <Input
        value={value || ''}
        onChange={(e) => {
          setValue(e.target.value);
          onChange(e.target.value);
        }}
        placeholder={`${count} records...`}
        style={{
          fontSize: '1.1rem',
          border: '0',
        }}
      />
    </span>
  );
};

// Define a default UI for filtering
const DefaultColumnFilter = ({
  column: { filterValue, preFilteredRows, setFilter },
}) => {
  const count = preFilteredRows.length;

  return (
    <Input
      value={filterValue || ''}
      onChange={(e) => {
        setFilter(e.target.value || undefined); // Set undefined to remove the filter entirely
      }}
      placeholder={`Search ${count} records...`}
    />
  );
};

// This is a custom filter UI for selecting
// a unique option from a list
const SelectColumnFilter = ({
  column: { filterValue, setFilter, preFilteredRows, id },
}) => {
  // Calculate the options for filtering
  // using the preFilteredRows
  const options = React.useMemo(() => {
    const options = new Set();
    preFilteredRows.forEach((row) => {
      options.add(row.values[id]);
    });
    return [...options.values()];
  }, [id, preFilteredRows]);

  // Render a multi-select box
  return (
    <select
      value={filterValue}
      onChange={(e) => {
        setFilter(e.target.value || undefined);
      }}
    >
      <option value="">All</option>
      {options.map((option, i) => (
        <option key={i} value={option}>
          {option}
        </option>
      ))}
    </select>
  );
};

// This is a custom filter UI that uses a
// slider to set the filter value between a column's
// min and max values
const SliderColumnFilter = ({
  column: { filterValue, setFilter, preFilteredRows, id },
}) => {
  // Calculate the min and max
  // using the preFilteredRows

  const [min, max] = React.useMemo(() => {
    let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
    let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
    preFilteredRows.forEach((row) => {
      min = Math.min(row.values[id], min);
      max = Math.max(row.values[id], max);
    });
    return [min, max];
  }, [id, preFilteredRows]);

  return (
    <>
      <Input
        type="range"
        min={min}
        max={max}
        value={filterValue || min}
        onChange={(e) => {
          setFilter(parseInt(e.target.value, 10));
        }}
      />
      <Button onClick={() => setFilter(undefined)}>Off</Button>
    </>
  );
};

// This is a custom UI for our 'between' or number range
// filter. It uses two number boxes and filters rows to
// ones that have values between the two
const NumberRangeColumnFilter = ({
  column: { filterValue = [], preFilteredRows, setFilter, id },
}) => {
  const [min, max] = React.useMemo(() => {
    let min = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
    let max = preFilteredRows.length ? preFilteredRows[0].values[id] : 0;
    preFilteredRows.forEach((row) => {
      min = Math.min(row.values[id], min);
      max = Math.max(row.values[id], max);
    });
    return [min, max];
  }, [id, preFilteredRows]);

  return (
    <div
      style={{
        display: 'flex',
      }}
    >
      <Input
        value={filterValue[0] || ''}
        type="number"
        onChange={(e) => {
          const val = e.target.value;
          setFilter((old = []) => [
            val ? parseInt(val, 10) : undefined,
            old[1],
          ]);
        }}
        placeholder={`Min (${min})`}
        style={{
          width: '70px',
          marginRight: '0.5rem',
        }}
      />
      to
      <Input
        value={filterValue[1] || ''}
        type="number"
        onChange={(e) => {
          const val = e.target.value;
          setFilter((old = []) => [
            old[0],
            val ? parseInt(val, 10) : undefined,
          ]);
        }}
        placeholder={`Max (${max})`}
        style={{
          width: '70px',
          marginLeft: '0.5rem',
        }}
      />
    </div>
  );
};

const fuzzyTextFilterFn = (rows, id, filterValue) =>
  matchSorter(rows, filterValue, { keys: [(row) => row.values[id]] });

// Let the table remove the filter if the string is empty
fuzzyTextFilterFn.autoRemove = (val) => !val;

// const ReactTable = "code goes here";
// Define a custom filter filter function!
function filterGreaterThan(rows, id, filterValue) {
  return rows.filter((row) => {
    const rowValue = row.values[id];
    return rowValue >= filterValue;
  });
}

// This is an autoRemove method on the filter function that
// when given the new filter value and returns true, the filter
// will be automatically removed. Normally this is just an undefined
// check, but here, we want to remove the filter if it's not a number
filterGreaterThan.autoRemove = (val) => typeof val !== 'number';
// const SearchYCDocsRT = "code goes here"
export default SearchYCDocsRT;

Here is the traceback message in Volto:

✔ Client
  Compiled successfully in 726.31ms

✔ Server
  Compiled successfully in 1.27s


TypeError: Cannot read property 'forEach' of undefined

  270 |   );
  271 | 
> 272 |   const {
  273 |     getTableProps,
  274 |     // getTableBodyProps,
  275 |     headerGroups,

    at accessRowsForColumn (/Users/rhennebrown/Plone524/frontend/v1312/node_modules/react-table/src/hooks/useTable.js:591:8)
    at /Users/rhennebrown/Plone524/frontend/v1312/node_modules/react-table/src/hooks/useTable.js:195:7
    at Object.useMemo (/Users/rhennebrown/Plone524/frontend/v1312/node_modules/react-dom/cjs/react-dom-server.node.development.js:1239:19)
    at Object.useMemo (/Users/rhennebrown/Plone524/frontend/v1312/node_modules/react/cjs/react.development.js:1521:21)
    at useTable (/Users/rhennebrown/Plone524/frontend/v1312/node_modules/react-table/src/hooks/useTable.js:186:44)
    at ReactTable (/Users/rhennebrown/Plone524/frontend/v1312/src/addons/volto-yc-documents/src/components/Accesories/Search/SearchRT.jsx:272:1)
    at processChild (/Users/rhennebrown/Plone524/frontend/v1312/node_modules/react-dom/cjs/react-dom-server.node.development.js:3043:14)
    at resolve (/Users/rhennebrown/Plone524/frontend/v1312/node_modules/react-dom/cjs/react-dom-server.node.development.js:2960:5)
    at ReactDOMServerRenderer.render (/Users/rhennebrown/Plone524/frontend/v1312/node_modules/react-dom/cjs/react-dom-server.node.development.js:3435:22)
    at ReactDOMServerRenderer.read (/Users/rhennebrown/Plone524/frontend/v1312/node_modules/react-dom/cjs/react-dom-server.node.development.js:3373:29)



I have not worked with volto (and not much with react), but what happens if you define 'preFilteredRows' 'manually'. Could it be that preFilteredRows actually is undefined ?

There's a pattern that you need to be aware of, practically everywhere when dealing with async stuff.

  • First, you have a component that renders.
  • As a render/use effect (React.useEffect), it triggers the async call.
  • That async call makes available some data.
  • You want to show that data in the component.

So when it first renders, your component will not have the async data. You have to take this into account.

For your case, I think you need to do something like this:

const SelectColumnFilter = ({
  column: { filterValue, setFilter, preFilteredRows=[], id },
}) 

@rbrown12 This practically should solve your use case!

Thanks to those who responded. I was wondering if there was a way to display static data in the data parameter instead of using it through MakeData as mentioned in the examples on the react-table site or would i need to have it automatically populated only? The reason is because i have options in the table for the user to click on a link to open up a Modal within the site.

const data = React.useMemo(
    () => [
      {
        number: number,
        incomingseek: incomingseek,
        created: created,
        empid: empid,
        firstname: firstname,
        lastname: lastname,
        documents: documents,
        state: state,
        action: action,
      },
    ],
    [],
  );