diff --git a/examples/filtering/sandbox.config.json b/examples/filtering/sandbox.config.json
new file mode 100644
index 0000000..0ef8d24
--- /dev/null
+++ b/examples/filtering/sandbox.config.json
@@ -0,0 +1,3 @@
+{
+ "infiniteLoopProtection": false
+}
diff --git a/examples/filtering/src/App.js b/examples/filtering/src/App.js
index 80d31f6..635f33a 100644
--- a/examples/filtering/src/App.js
+++ b/examples/filtering/src/App.js
@@ -36,7 +36,9 @@ const Styles = styled.div`
`
// Define a default UI for filtering
-function DefaultColumnFilter({ filterValue, preFilteredRows, setFilter }) {
+function DefaultColumnFilter({
+ column: { filterValue, preFilteredRows, setFilter },
+}) {
const count = preFilteredRows.length
return (
@@ -52,19 +54,18 @@ function DefaultColumnFilter({ filterValue, preFilteredRows, setFilter }) {
// This is a custom filter UI for selecting
// a unique option from a list
-function SelectColumnFilter({ filterValue, setFilter, preFilteredRows, id }) {
+function 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]
- )
+ 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 (
@@ -87,22 +88,21 @@ function SelectColumnFilter({ filterValue, setFilter, preFilteredRows, id }) {
// This is a custom filter UI that uses a
// slider to set the filter value between a column's
// min and max values
-function SliderColumnFilter({ filterValue, setFilter, preFilteredRows, id }) {
+function SliderColumnFilter({
+ column: { filterValue, setFilter, preFilteredRows, id },
+}) {
// Calculate the min and max
// using the preFilteredRows
- const [min, max] = React.useMemo(
- () => {
- let min = 0
- let max = 0
- preFilteredRows.forEach(row => {
- min = Math.min(row.values[id], min)
- max = Math.max(row.values[id], max)
- })
- return [min, max]
- },
- [id, 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 (
<>
@@ -124,23 +124,17 @@ function SliderColumnFilter({ filterValue, setFilter, preFilteredRows, id }) {
// filter. It uses two number boxes and filters rows to
// ones that have values between the two
function NumberRangeColumnFilter({
- filterValue = [],
- preFilteredRows,
- setFilter,
- id,
+ column: { filterValue = [], preFilteredRows, setFilter, id },
}) {
- const [min, max] = React.useMemo(
- () => {
- let min = 0
- let max = 0
- preFilteredRows.forEach(row => {
- min = Math.min(row.values[id], min)
- max = Math.max(row.values[id], max)
- })
- return [min, max]
- },
- [id, 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 (
@@ -337,7 +331,7 @@ function App() {
[]
)
- const data = React.useMemo(() => makeData(10000), [])
+ const data = React.useMemo(() => makeData(100000), [])
return (
diff --git a/package.json b/package.json
index 84bb82e..fffa804 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,7 @@
"jest-watch-select-projects": "^0.1.2",
"jest-watch-typeahead": "^0.3.1",
"lint-staged": "^9.2.1",
+ "prettier": "^1.18.2",
"prop-types": "^15.5.0",
"react": "^16.9.0",
"react-dom": "^16.9.0",
diff --git a/src/hooks/useTable.js b/src/hooks/useTable.js
index dd4d684..5c486b4 100755
--- a/src/hooks/useTable.js
+++ b/src/hooks/useTable.js
@@ -102,30 +102,27 @@ export const useTable = (props, ...plugins) => {
])
// Allow hooks to decorate columns (and trigger this memoization via deps)
- columns = React.useMemo(
- () => {
- if (process.env.NODE_ENV === 'development' && debug)
- console.time('hooks.columnsBeforeHeaderGroups')
- const newColumns = applyHooks(
- instanceRef.current.hooks.columnsBeforeHeaderGroups,
- columns,
- instanceRef.current
- )
- if (process.env.NODE_ENV === 'development' && debug)
- console.timeEnd('hooks.columnsBeforeHeaderGroups')
- return newColumns
- },
- [
+ columns = React.useMemo(() => {
+ if (process.env.NODE_ENV === 'development' && debug)
+ console.time('hooks.columnsBeforeHeaderGroups')
+ const newColumns = applyHooks(
+ instanceRef.current.hooks.columnsBeforeHeaderGroups,
columns,
- debug,
- // eslint-disable-next-line react-hooks/exhaustive-deps
- ...applyHooks(
- instanceRef.current.hooks.columnsBeforeHeaderGroupsDeps,
- [],
- instanceRef.current
- ),
- ]
- )
+ instanceRef.current
+ )
+ if (process.env.NODE_ENV === 'development' && debug)
+ console.timeEnd('hooks.columnsBeforeHeaderGroups')
+ return newColumns
+ }, [
+ columns,
+ debug,
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ ...applyHooks(
+ instanceRef.current.hooks.columnsBeforeHeaderGroupsDeps,
+ [],
+ instanceRef.current
+ ),
+ ])
// Make the headerGroups
const headerGroups = React.useMemo(
@@ -144,72 +141,69 @@ export const useTable = (props, ...plugins) => {
})
// Access the row model
- const [rows, rowPaths, flatRows] = React.useMemo(
- () => {
- if (process.env.NODE_ENV === 'development' && debug)
- console.time('getAccessedRows')
+ const [rows, rowPaths, flatRows] = React.useMemo(() => {
+ if (process.env.NODE_ENV === 'development' && debug)
+ console.time('getAccessedRows')
- let flatRows = 0
- const rowPaths = []
+ let flatRows = 0
+ const rowPaths = []
- // Access the row's data
- const accessRow = (originalRow, i, depth = 0, parentPath = []) => {
- // Keep the original reference around
- const original = originalRow
+ // Access the row's data
+ const accessRow = (originalRow, i, depth = 0, parentPath = []) => {
+ // Keep the original reference around
+ const original = originalRow
- // Make the new path for the row
- const path = [...parentPath, i]
+ // Make the new path for the row
+ const path = [...parentPath, i]
- flatRows++
- rowPaths.push(path.join('.'))
+ flatRows++
+ rowPaths.push(path.join('.'))
- // Process any subRows
- const subRows = originalRow[subRowsKey]
- ? originalRow[subRowsKey].map((d, i) =>
- accessRow(d, i, depth + 1, path)
- )
- : []
-
- const row = {
- original,
- index: i,
- path, // used to create a key for each row even if not nested
- subRows,
- depth,
- cells: [{}], // This is a dummy cell
- }
-
- // Override common array functions (and the dummy cell's getCellProps function)
- // to show an error if it is accessed without calling prepareRow
- const unpreparedAccessWarning = () => {
- throw new Error(
- 'React-Table: You have not called prepareRow(row) one or more rows you are attempting to render.'
+ // Process any subRows
+ const subRows = originalRow[subRowsKey]
+ ? originalRow[subRowsKey].map((d, i) =>
+ accessRow(d, i, depth + 1, path)
)
- }
- row.cells.map = unpreparedAccessWarning
- row.cells.filter = unpreparedAccessWarning
- row.cells.forEach = unpreparedAccessWarning
- row.cells[0].getCellProps = unpreparedAccessWarning
+ : []
- // Create the cells and values
- row.values = {}
- instanceRef.current.columns.forEach(column => {
- row.values[column.id] = column.accessor
- ? column.accessor(originalRow, i, { subRows, depth, data })
- : undefined
- })
-
- return row
+ const row = {
+ original,
+ index: i,
+ path, // used to create a key for each row even if not nested
+ subRows,
+ depth,
+ cells: [{}], // This is a dummy cell
}
- // Use the resolved data
- const accessedData = data.map((d, i) => accessRow(d, i))
- if (process.env.NODE_ENV === 'development' && debug)
- console.timeEnd('getAccessedRows')
- return [accessedData, rowPaths, flatRows]
- },
- [debug, data, subRowsKey]
- )
+ // Override common array functions (and the dummy cell's getCellProps function)
+ // to show an error if it is accessed without calling prepareRow
+ const unpreparedAccessWarning = () => {
+ throw new Error(
+ 'React-Table: You have not called prepareRow(row) one or more rows you are attempting to render.'
+ )
+ }
+ row.cells.map = unpreparedAccessWarning
+ row.cells.filter = unpreparedAccessWarning
+ row.cells.forEach = unpreparedAccessWarning
+ row.cells[0].getCellProps = unpreparedAccessWarning
+
+ // Create the cells and values
+ row.values = {}
+ instanceRef.current.columns.forEach(column => {
+ row.values[column.id] = column.accessor
+ ? column.accessor(originalRow, i, { subRows, depth, data })
+ : undefined
+ })
+
+ return row
+ }
+
+ // Use the resolved data
+ const accessedData = data.map((d, i) => accessRow(d, i))
+ if (process.env.NODE_ENV === 'development' && debug)
+ console.timeEnd('getAccessedRows')
+ return [accessedData, rowPaths, flatRows]
+ }, [debug, data, subRowsKey])
instanceRef.current.rows = rows
instanceRef.current.rowPaths = rowPaths
diff --git a/src/plugin-hooks/useFilters.js b/src/plugin-hooks/useFilters.js
index 4325161..075f6a9 100755
--- a/src/plugin-hooks/useFilters.js
+++ b/src/plugin-hooks/useFilters.js
@@ -129,92 +129,87 @@ function useMain(instance) {
// cache for each row group (top-level rows, and each row's recursive subrows)
// This would make multi-filtering a lot faster though. Too far?
- const filteredRows = React.useMemo(
- () => {
- if (manualFilters || !Object.keys(filters).length) {
- return rows
- }
+ const filteredRows = React.useMemo(() => {
+ if (manualFilters || !Object.keys(filters).length) {
+ return rows
+ }
- if (process.env.NODE_ENV === 'development' && debug)
- console.info('getFilteredRows')
+ if (process.env.NODE_ENV === 'development' && debug)
+ console.info('getFilteredRows')
- // Filters top level and nested rows
- const filterRows = (rows, depth = 0) => {
- let filteredRows = rows
+ // Filters top level and nested rows
+ const filterRows = (rows, depth = 0) => {
+ let filteredRows = rows
- filteredRows = Object.entries(filters).reduce(
- (filteredSoFar, [columnID, filterValue]) => {
- // Find the filters column
- const column = columns.find(d => d.id === columnID)
+ filteredRows = Object.entries(filters).reduce(
+ (filteredSoFar, [columnID, filterValue]) => {
+ // Find the filters column
+ const column = columns.find(d => d.id === columnID)
- if (depth === 0) {
- column.preFilteredRows = filteredSoFar
- }
+ if (depth === 0) {
+ column.preFilteredRows = filteredSoFar
+ }
- if (!column) {
- return filteredSoFar
- }
+ if (!column) {
+ return filteredSoFar
+ }
- const filterMethod = getFilterMethod(
- column.filter,
- userFilterTypes || {},
- filterTypes
+ const filterMethod = getFilterMethod(
+ column.filter,
+ userFilterTypes || {},
+ filterTypes
+ )
+
+ if (!filterMethod) {
+ console.warn(
+ `Could not find a valid 'column.filter' for column with the ID: ${column.id}.`
)
-
- if (!filterMethod) {
- console.warn(
- `Could not find a valid 'column.filter' for column with the ID: ${
- column.id
- }.`
- )
- return filteredSoFar
- }
-
- // Pass the rows, id, filterValue and column to the filterMethod
- // to get the filtered rows back
- return filterMethod(filteredSoFar, columnID, filterValue, column)
- },
- rows
- )
-
- // Apply the filter to any subRows
- // We technically could do this recursively in the above loop,
- // but that would severely hinder the API for the user, since they
- // would be required to do that recursion in some scenarios
- filteredRows = filteredRows.map(row => {
- if (!row.subRows) {
- return row
+ return filteredSoFar
}
- return {
- ...row,
- subRows:
- row.subRows && row.subRows.length > 0
- ? filterRows(row.subRows, depth + 1)
- : row.subRows,
- }
- })
- return filteredRows
- }
-
- const filteredRows = filterRows(rows)
-
- // Now that each filtered column has it's partially filtered rows,
- // lets assign the final filtered rows to all of the other columns
- const nonFilteredColumns = columns.filter(
- column => !Object.keys(filters).includes(column.id)
+ // Pass the rows, id, filterValue and column to the filterMethod
+ // to get the filtered rows back
+ return filterMethod(filteredSoFar, columnID, filterValue, column)
+ },
+ rows
)
- // This essentially enables faceted filter options to be built easily
- // using every column's preFilteredRows value
- nonFilteredColumns.forEach(column => {
- column.preFilteredRows = filteredRows
+ // Apply the filter to any subRows
+ // We technically could do this recursively in the above loop,
+ // but that would severely hinder the API for the user, since they
+ // would be required to do that recursion in some scenarios
+ filteredRows = filteredRows.map(row => {
+ if (!row.subRows) {
+ return row
+ }
+ return {
+ ...row,
+ subRows:
+ row.subRows && row.subRows.length > 0
+ ? filterRows(row.subRows, depth + 1)
+ : row.subRows,
+ }
})
return filteredRows
- },
- [manualFilters, filters, debug, rows, columns, userFilterTypes]
- )
+ }
+
+ return filterRows(rows)
+ }, [manualFilters, filters, debug, rows, columns, userFilterTypes])
+
+ React.useMemo(() => {
+ // Now that each filtered column has it's partially filtered rows,
+ // lets assign the final filtered rows to all of the other columns
+ const nonFilteredColumns = columns.filter(
+ column => !Object.keys(filters).includes(column.id)
+ )
+
+ // This essentially enables faceted filter options to be built easily
+ // using every column's preFilteredRows value
+ nonFilteredColumns.forEach(column => {
+ column.preFilteredRows = filteredRows
+ })
+ }, [columns, filteredRows, filters])
return {
...instance,
diff --git a/src/plugin-hooks/usePagination.js b/src/plugin-hooks/usePagination.js
index 341e2de..b3810ca 100755
--- a/src/plugin-hooks/usePagination.js
+++ b/src/plugin-hooks/usePagination.js
@@ -52,46 +52,40 @@ function useMain(instance) {
const isPageIndexMountedRef = React.useRef()
- useLayoutEffect(
- () => {
- if (isPageIndexMountedRef.current) {
- setState(
- old => ({
- ...old,
- pageIndex: 0,
- }),
- actions.pageChange
- )
- }
- isPageIndexMountedRef.current = true
- },
- [setState, rowDep, filters, groupBy, sortBy]
- )
+ useLayoutEffect(() => {
+ if (isPageIndexMountedRef.current) {
+ setState(
+ old => ({
+ ...old,
+ pageIndex: 0,
+ }),
+ actions.pageChange
+ )
+ }
+ isPageIndexMountedRef.current = true
+ }, [setState, rowDep, filters, groupBy, sortBy])
- const pages = React.useMemo(
- () => {
- if (manualPagination) {
- return undefined
- }
- if (process.env.NODE_ENV === 'development' && debug)
- console.info('getPages')
+ const pages = React.useMemo(() => {
+ if (manualPagination) {
+ return undefined
+ }
+ if (process.env.NODE_ENV === 'development' && debug)
+ console.info('getPages')
- // Create a new pages with the first page ready to go.
- const pages = rows.length ? [] : [[]]
+ // Create a new pages with the first page ready to go.
+ const pages = rows.length ? [] : [[]]
- // Start the pageIndex and currentPage cursors
- let cursor = 0
+ // Start the pageIndex and currentPage cursors
+ let cursor = 0
- while (cursor < rows.length) {
- const end = cursor + pageSize
- pages.push(rows.slice(cursor, end))
- cursor = end
- }
+ while (cursor < rows.length) {
+ const end = cursor + pageSize
+ pages.push(rows.slice(cursor, end))
+ cursor = end
+ }
- return pages
- },
- [debug, manualPagination, pageSize, rows]
- )
+ return pages
+ }, [debug, manualPagination, pageSize, rows])
const pageCount = manualPagination ? userPageCount : pages.length
diff --git a/src/plugin-hooks/useRowState.js b/src/plugin-hooks/useRowState.js
index eefd247..5d2c92d 100644
--- a/src/plugin-hooks/useRowState.js
+++ b/src/plugin-hooks/useRowState.js
@@ -72,21 +72,18 @@ function useMain(instance) {
const rowsMountedRef = React.useRef()
// When data changes, reset row and cell state
- React.useEffect(
- () => {
- if (rowsMountedRef.current) {
- setState(old => {
- return {
- ...old,
- rowState: {},
- }
- }, actions.setRowState)
- }
+ React.useEffect(() => {
+ if (rowsMountedRef.current) {
+ setState(old => {
+ return {
+ ...old,
+ rowState: {},
+ }
+ }, actions.setRowState)
+ }
- rowsMountedRef.current = true
- },
- [rows, setState]
- )
+ rowsMountedRef.current = true
+ }, [rows, setState])
hooks.prepareRow.push(row => {
const pathKey = row.path.join('.')
diff --git a/src/plugin-hooks/useSortBy.js b/src/plugin-hooks/useSortBy.js
index 3008ccf..e602a80 100755
--- a/src/plugin-hooks/useSortBy.js
+++ b/src/plugin-hooks/useSortBy.js
@@ -202,72 +202,69 @@ function useMain(instance) {
column.sortedDesc = column.sorted ? column.sorted.desc : undefined
})
- const sortedRows = React.useMemo(
- () => {
- if (manualSorting || !sortBy.length) {
- return rows
- }
- if (process.env.NODE_ENV === 'development' && debug)
- console.time('getSortedRows')
+ const sortedRows = React.useMemo(() => {
+ if (manualSorting || !sortBy.length) {
+ return rows
+ }
+ if (process.env.NODE_ENV === 'development' && debug)
+ console.time('getSortedRows')
- const sortData = rows => {
- // Use the orderByFn to compose multiple sortBy's together.
- // This will also perform a stable sorting using the row index
- // if needed.
- const sortedData = orderByFn(
- rows,
- sortBy.map(sort => {
- // Support custom sorting methods for each column
- const { sortType } = columns.find(d => d.id === sort.id)
+ const sortData = rows => {
+ // Use the orderByFn to compose multiple sortBy's together.
+ // This will also perform a stable sorting using the row index
+ // if needed.
+ const sortedData = orderByFn(
+ rows,
+ sortBy.map(sort => {
+ // Support custom sorting methods for each column
+ const { sortType } = columns.find(d => d.id === sort.id)
- // Look up sortBy functions in this order:
- // column function
- // column string lookup on user sortType
- // column string lookup on built-in sortType
- // default function
- // default string lookup on user sortType
- // default string lookup on built-in sortType
- const sortMethod =
- isFunction(sortType) ||
- (userSortTypes || {})[sortType] ||
- sortTypes[sortType] ||
- sortTypes.alphanumeric
+ // Look up sortBy functions in this order:
+ // column function
+ // column string lookup on user sortType
+ // column string lookup on built-in sortType
+ // default function
+ // default string lookup on user sortType
+ // default string lookup on built-in sortType
+ const sortMethod =
+ isFunction(sortType) ||
+ (userSortTypes || {})[sortType] ||
+ sortTypes[sortType] ||
+ sortTypes.alphanumeric
- // Return the correct sortFn
- return (a, b) =>
- sortMethod(a.values[sort.id], b.values[sort.id], sort.desc)
- }),
- // Map the directions
- sortBy.map(sort => {
- // Detect and use the sortInverted option
- const { sortInverted } = columns.find(d => d.id === sort.id)
+ // Return the correct sortFn
+ return (a, b) =>
+ sortMethod(a.values[sort.id], b.values[sort.id], sort.desc)
+ }),
+ // Map the directions
+ sortBy.map(sort => {
+ // Detect and use the sortInverted option
+ const { sortInverted } = columns.find(d => d.id === sort.id)
- if (sortInverted) {
- return sort.desc
- }
-
- return !sort.desc
- })
- )
-
- // If there are sub-rows, sort them
- sortedData.forEach(row => {
- if (!row.subRows || row.subRows.length <= 1) {
- return
+ if (sortInverted) {
+ return sort.desc
}
- row.subRows = sortData(row.subRows)
+
+ return !sort.desc
})
+ )
- return sortedData
- }
+ // If there are sub-rows, sort them
+ sortedData.forEach(row => {
+ if (!row.subRows || row.subRows.length <= 1) {
+ return
+ }
+ row.subRows = sortData(row.subRows)
+ })
- if (process.env.NODE_ENV === 'development' && debug)
- console.timeEnd('getSortedRows')
+ return sortedData
+ }
- return sortData(rows)
- },
- [manualSorting, sortBy, debug, columns, rows, orderByFn, userSortTypes]
- )
+ if (process.env.NODE_ENV === 'development' && debug)
+ console.timeEnd('getSortedRows')
+
+ return sortData(rows)
+ }, [manualSorting, sortBy, debug, columns, rows, orderByFn, userSortTypes])
return {
...instance,
diff --git a/yarn.lock b/yarn.lock
index 6a4e5a8..82da0a0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6320,6 +6320,11 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
+prettier@^1.18.2:
+ version "1.18.2"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
+ integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
+
pretty-format@^23.6.0:
version "23.6.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760"