react-table
Version:
Hooks for building lightweight, fast and extendable datagrids for React
304 lines (257 loc) • 7.46 kB
JavaScript
import React from 'react'
import {
getFirstDefined,
getFilterMethod,
shouldAutoRemoveFilter,
} from '../utils'
import {
actions,
useGetLatest,
functionalUpdate,
useMountedLayoutEffect,
} from '../publicUtils'
import * as filterTypes from '../filterTypes'
// Actions
actions.resetFilters = 'resetFilters'
actions.setFilter = 'setFilter'
actions.setAllFilters = 'setAllFilters'
export const useFilters = hooks => {
hooks.stateReducers.push(reducer)
hooks.useInstance.push(useInstance)
}
useFilters.pluginName = 'useFilters'
function reducer(state, action, previousState, instance) {
if (action.type === actions.init) {
return {
filters: [],
...state,
}
}
if (action.type === actions.resetFilters) {
return {
...state,
filters: instance.initialState.filters || [],
}
}
if (action.type === actions.setFilter) {
const { columnId, filterValue } = action
const { allColumns, filterTypes: userFilterTypes } = instance
const column = allColumns.find(d => d.id === columnId)
if (!column) {
throw new Error(
`React-Table: Could not find a column with id: ${columnId}`
)
}
const filterMethod = getFilterMethod(
column.filter,
userFilterTypes || {},
filterTypes
)
const previousfilter = state.filters.find(d => d.id === columnId)
const newFilter = functionalUpdate(
filterValue,
previousfilter && previousfilter.value
)
//
if (shouldAutoRemoveFilter(filterMethod.autoRemove, newFilter, column)) {
return {
...state,
filters: state.filters.filter(d => d.id !== columnId),
}
}
if (previousfilter) {
return {
...state,
filters: state.filters.map(d => {
if (d.id === columnId) {
return { id: columnId, value: newFilter }
}
return d
}),
}
}
return {
...state,
filters: [...state.filters, { id: columnId, value: newFilter }],
}
}
if (action.type === actions.setAllFilters) {
const { filters } = action
const { allColumns, filterTypes: userFilterTypes } = instance
return {
...state,
// Filter out undefined values
filters: functionalUpdate(filters, state.filters).filter(filter => {
const column = allColumns.find(d => d.id === filter.id)
const filterMethod = getFilterMethod(
column.filter,
userFilterTypes || {},
filterTypes
)
if (
shouldAutoRemoveFilter(filterMethod.autoRemove, filter.value, column)
) {
return false
}
return true
}),
}
}
}
function useInstance(instance) {
const {
data,
rows,
flatRows,
rowsById,
allColumns,
filterTypes: userFilterTypes,
manualFilters,
defaultCanFilter = false,
disableFilters,
state: { filters },
dispatch,
autoResetFilters = true,
} = instance
const setFilter = React.useCallback(
(columnId, filterValue) => {
dispatch({ type: actions.setFilter, columnId, filterValue })
},
[dispatch]
)
const setAllFilters = React.useCallback(
filters => {
dispatch({
type: actions.setAllFilters,
filters,
})
},
[dispatch]
)
allColumns.forEach(column => {
const {
id,
accessor,
defaultCanFilter: columnDefaultCanFilter,
disableFilters: columnDisableFilters,
} = column
// Determine if a column is filterable
column.canFilter = accessor
? getFirstDefined(
columnDisableFilters === true ? false : undefined,
disableFilters === true ? false : undefined,
true
)
: getFirstDefined(columnDefaultCanFilter, defaultCanFilter, false)
// Provide the column a way of updating the filter value
column.setFilter = val => setFilter(column.id, val)
// Provide the current filter value to the column for
// convenience
const found = filters.find(d => d.id === id)
column.filterValue = found && found.value
})
const [
filteredRows,
filteredFlatRows,
filteredRowsById,
] = React.useMemo(() => {
if (manualFilters || !filters.length) {
return [rows, flatRows, rowsById]
}
const filteredFlatRows = []
const filteredRowsById = {}
// Filters top level and nested rows
const filterRows = (rows, depth = 0) => {
let filteredRows = rows
filteredRows = filters.reduce(
(filteredSoFar, { id: columnId, value: filterValue }) => {
// Find the filters column
const column = allColumns.find(d => d.id === columnId)
if (!column) {
return filteredSoFar
}
if (depth === 0) {
column.preFilteredRows = filteredSoFar
}
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}.`
)
return filteredSoFar
}
// Pass the rows, id, filterValue and column to the filterMethod
// to get the filtered rows back
column.filteredRows = filterMethod(
filteredSoFar,
[columnId],
filterValue
)
return column.filteredRows
},
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.forEach(row => {
filteredFlatRows.push(row)
filteredRowsById[row.id] = row
if (!row.subRows) {
return
}
row.subRows =
row.subRows && row.subRows.length > 0
? filterRows(row.subRows, depth + 1)
: row.subRows
})
return filteredRows
}
return [filterRows(rows), filteredFlatRows, filteredRowsById]
}, [
manualFilters,
filters,
rows,
flatRows,
rowsById,
allColumns,
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 = allColumns.filter(
column => !filters.find(d => d.id === column.id)
)
// This essentially enables faceted filter options to be built easily
// using every column's preFilteredRows value
nonFilteredColumns.forEach(column => {
column.preFilteredRows = filteredRows
column.filteredRows = filteredRows
})
}, [filteredRows, filters, allColumns])
const getAutoResetFilters = useGetLatest(autoResetFilters)
useMountedLayoutEffect(() => {
if (getAutoResetFilters()) {
dispatch({ type: actions.resetFilters })
}
}, [dispatch, manualFilters ? null : data])
Object.assign(instance, {
preFilteredRows: rows,
preFilteredFlatRows: flatRows,
preFilteredRowsById: rowsById,
filteredRows,
filteredFlatRows,
filteredRowsById,
rows: filteredRows,
flatRows: filteredFlatRows,
rowsById: filteredRowsById,
setFilter,
setAllFilters,
})
}