From 3187061041587216b9dc4f7bce3eabcbf9e0244d Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Wed, 20 Nov 2019 12:09:24 -0700 Subject: [PATCH] Use effect dependency user call --- .size-snapshot.json | 12 ++-- CHANGELOG.md | 9 +++ docs/api.md | 43 +++++++++-- examples/editable-data/src/App.js | 32 ++++----- examples/kitchen-sink-controlled/src/App.js | 71 +++++++++--------- examples/kitchen-sink/src/App.js | 80 ++++++++++----------- index.d.ts | 7 +- src/plugin-hooks/useExpanded.js | 28 +++++++- src/plugin-hooks/useFilters.js | 18 ++++- src/plugin-hooks/usePagination.js | 28 ++++---- src/plugin-hooks/useRowSelect.js | 25 ++++--- src/plugin-hooks/useSortBy.js | 21 +++++- 12 files changed, 237 insertions(+), 137 deletions(-) diff --git a/.size-snapshot.json b/.size-snapshot.json index 245c1fc..f14036e 100644 --- a/.size-snapshot.json +++ b/.size-snapshot.json @@ -1,13 +1,13 @@ { "dist/index.js": { - "bundled": 87904, - "minified": 41988, - "gzipped": 11252 + "bundled": 89986, + "minified": 42988, + "gzipped": 11389 }, "dist/index.es.js": { - "bundled": 87321, - "minified": 41480, - "gzipped": 11132, + "bundled": 89403, + "minified": 42480, + "gzipped": 11267, "treeshaked": { "rollup": { "code": 428, diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db21b5..9fba370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ ## 7.0.0-beta.14 +- Removed + - `disablePageResetOnDataChange` option. use the `getResetPageDeps` option now. +- Added + - `getResetPageDeps` option + - `getResetFilterDeps` option + - `getResetSortByDeps` option + - `getResetGroupByDeps` option + - `getResetExpandedDeps` option + ## 7.0.0-beta.13 - Added options diff --git a/docs/api.md b/docs/api.md index 4b660b6..989da2b 100644 --- a/docs/api.md +++ b/docs/api.md @@ -384,6 +384,11 @@ The following options are supported via the main options object passed to `useTa - Must be **memoized** - Allows overriding or adding additional sort types for columns to use. If a column's sort type isn't found on this object, it will default to using the built-in sort types. - For more information on sort types, see Sorting +- `getResetSortByDeps: Function(instance) => [...useEffectDependencies]` + - Optional + - Defaults to `false` + - If set, the dependencies returned from this function will be used to determine when the effect to reset the `sortBy` state is fired. + - To disable, set to `false` ### Column Options @@ -487,6 +492,11 @@ The following options are supported via the main options object passed to `useTa - Must be **memoized** - Allows overriding or adding additional filter types for columns to use. If a column's filter type isn't found on this object, it will default to using the built-in filter types. - For more information on filter types, see Filtering +- `getResetFiltersDeps: Function(instance) => [...useEffectDependencies]` + - Optional + - Defaults to `false` + - If set, the dependencies returned from this function will be used to determine when the effect to reset the `filters` state is fired. + - To disable, set to `false` ### Column Options @@ -696,6 +706,14 @@ The following options are supported via the main options object passed to `useTa - Defaults to `true` - If set to `true`, expanded rows are rendered along with normal rows. - If set to `false`, expanded rows will only be available through their parent row. This could be useful if you are implementing a custom expanded row view. +- `getResetExpandedDeps: Function(instance) => [...useEffectDependencies]` + - Optional + - Defaults to resetting the `expanded` state to `[]` when the dependencies below change + - ```js + const getResetExpandedDeps = ({ data }) => [data] + ``` + - If set, the dependencies returned from this function will be used to determine when the effect to reset the `expanded` state is fired. + - To disable, set to `false` ### Instance Properties @@ -750,10 +768,19 @@ The following options are supported via the main options object passed to `useTa - `manualPagination: Bool` - Enables pagination functionality, but does not automatically perform row pagination. - Turn this on if you wish to implement your own pagination outside of the table (eg. server-side pagination or any other manual pagination technique) -- `disablePageResetOnDataChange` - - Defaults to `false` - - Normally, any changes detected to `rows`, `state.filters`, `state.groupBy`, or `state.sortBy` will trigger the `pageIndex` to be reset to `0` - - If set to `true`, the `pageIndex` will not be automatically set to `0` when these dependencies change. +- `getResetPageDeps: Function(instance) => [...useEffectDependencies]` + - Optional + - Defaults to resetting the `pageIndex` to `0` when the dependencies below change + - ```js + const getResetPageDeps = ({ + rows, + manualPagination, + state: { filters, groupBy, sortBy }, + }) => [manualPagination ? null : rows, filters, groupBy, sortBy] + ``` + - Note that if `manualPagination` is set to `true`, then the pageIndex should not be reset when `rows` change + - If set, the dependencies returned from this function will be used to determine when the effect to reset the `pageIndex` state is fired. + - To disable, set to `false` - `paginateExpandedRows: Bool` - Optional - Only applies when using the `useExpanded` plugin hook simultaneously @@ -833,6 +860,14 @@ The following options are supported via the main options object passed to `useTa - Optional - Defaults to `isSelected` - If this key is found on the **original** data row, and it is true, this row will be manually selected +- `getResetSelectedRowPathsDeps: Function(instance) => [...useEffectDependencies]` + - Optional + - Defaults to resetting the `expanded` state to `[]` when the dependencies below change + - ```js + const getResetSelectedRowPathsDeps = ({ rows }) => [rows] + ``` + - If set, the dependencies returned from this function will be used to determine when the effect to reset the `selectedRowPaths` state is fired. + - To disable, set to `false` ### Instance Properties diff --git a/examples/editable-data/src/App.js b/examples/editable-data/src/App.js index 087a15b..a9c1c7f 100644 --- a/examples/editable-data/src/App.js +++ b/examples/editable-data/src/App.js @@ -76,8 +76,8 @@ const defaultColumn = { Cell: EditableCell, } -// Be sure to pass our updateMyData and the disablePageResetOnDataChange option -function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { +// Be sure to pass our updateMyData and the skipPageReset option +function Table({ columns, data, updateMyData, skipPageReset }) { // For this example, we're using pagination to illustrate how to stop // the current page from resetting when our data changes // Otherwise, nothing is different here. @@ -101,7 +101,8 @@ function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { columns, data, defaultColumn, - disablePageResetOnDataChange, + // use the skipPageReset option to disable page resetting temporarily + getResetPageDeps: skipPageReset ? false : undefined, // updateMyData isn't part of the API, but // anything we put into these options will // automatically be available on the instance. @@ -126,19 +127,16 @@ function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { ))} - {page.map( - (row, i) => { - prepareRow(row); - return ( - - {row.cells.map(cell => { - return ( - {cell.render('Cell')} - ) - })} - - )} - )} + {page.map((row, i) => { + prepareRow(row) + return ( + + {row.cells.map(cell => { + return {cell.render('Cell')} + })} + + ) + })}
@@ -274,7 +272,7 @@ function App() { columns={columns} data={data} updateMyData={updateMyData} - disablePageResetOnDataChange={skipPageReset} + skipPageReset={skipPageReset} /> ) diff --git a/examples/kitchen-sink-controlled/src/App.js b/examples/kitchen-sink-controlled/src/App.js index 2cecdd9..e8c24b9 100644 --- a/examples/kitchen-sink-controlled/src/App.js +++ b/examples/kitchen-sink-controlled/src/App.js @@ -232,8 +232,8 @@ function fuzzyTextFilterFn(rows, id, filterValue) { // Let the table remove the filter if the string is empty fuzzyTextFilterFn.autoRemove = val => !val -// Be sure to pass our updateMyData and the disablePageResetOnDataChange option -function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { +// Be sure to pass our updateMyData and the skipPageReset option +function Table({ columns, data, updateMyData, skipPageReset }) { const filterTypes = React.useMemo( () => ({ // Add a new fuzzyTextFilterFn filter type. @@ -305,8 +305,8 @@ function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { // cell renderer! updateMyData, // We also need to pass this so the page doesn't change - // when we edit the data - disablePageResetOnDataChange, + // when we edit the data, undefined means using the default + getResetPageDeps: skipPageReset ? false : undefined, }, useFilters, useSortBy, @@ -350,37 +350,36 @@ function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { ))} - {page.map( - row => { - prepareRow(row); - return ( - - {row.cells.map(cell => { - return ( - - {cell.isGrouped ? ( - // If it's a grouped cell, add an expander and row count - <> - - {row.isExpanded ? '👇' : '👉'} - {' '} - {cell.render('Cell', { editable: false })} ( - {row.subRows.length}) - - ) : cell.isAggregated ? ( - // If the cell is aggregated, use the Aggregated - // renderer for cell - cell.render('Aggregated') - ) : cell.isRepeatedValue ? null : ( // For cells with repeated values, render null - // Otherwise, just render the regular cell - cell.render('Cell', { editable: true }) - )} - - ) - })} - - )} - )} + {page.map(row => { + prepareRow(row) + return ( + + {row.cells.map(cell => { + return ( + + {cell.isGrouped ? ( + // If it's a grouped cell, add an expander and row count + <> + + {row.isExpanded ? '👇' : '👉'} + {' '} + {cell.render('Cell', { editable: false })} ( + {row.subRows.length}) + + ) : cell.isAggregated ? ( + // If the cell is aggregated, use the Aggregated + // renderer for cell + cell.render('Aggregated') + ) : cell.isRepeatedValue ? null : ( // For cells with repeated values, render null + // Otherwise, just render the regular cell + cell.render('Cell', { editable: true }) + )} + + ) + })} + + ) + })} {/* @@ -623,7 +622,7 @@ function App() { columns={columns} data={data} updateMyData={updateMyData} - disablePageResetOnDataChange={skipPageResetRef.current} + skipPageReset={skipPageResetRef.current} /> ) diff --git a/examples/kitchen-sink/src/App.js b/examples/kitchen-sink/src/App.js index 1ef6c61..cf6183e 100644 --- a/examples/kitchen-sink/src/App.js +++ b/examples/kitchen-sink/src/App.js @@ -232,8 +232,8 @@ function fuzzyTextFilterFn(rows, id, filterValue) { // Let the table remove the filter if the string is empty fuzzyTextFilterFn.autoRemove = val => !val -// Be sure to pass our updateMyData and the disablePageResetOnDataChange option -function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { +// Be sure to pass our updateMyData and the skipReset option +function Table({ columns, data, updateMyData, skipReset }) { const filterTypes = React.useMemo( () => ({ // Add a new fuzzyTextFilterFn filter type. @@ -305,8 +305,9 @@ function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { // cell renderer! updateMyData, // We also need to pass this so the page doesn't change - // when we edit the data - disablePageResetOnDataChange, + // when we edit the data. Undefined tells it to use the default + getResetPageDeps: skipReset ? false : undefined, + getResetSelectedRowPathsDeps: skipReset ? false : undefined, }, useFilters, useGroupBy, @@ -350,37 +351,36 @@ function Table({ columns, data, updateMyData, disablePageResetOnDataChange }) { ))} - {page.map( - row => { - prepareRow(row); - return ( - - {row.cells.map(cell => { - return ( - - {cell.isGrouped ? ( - // If it's a grouped cell, add an expander and row count - <> - - {row.isExpanded ? '👇' : '👉'} - {' '} - {cell.render('Cell', { editable: false })} ( - {row.subRows.length}) - - ) : cell.isAggregated ? ( - // If the cell is aggregated, use the Aggregated - // renderer for cell - cell.render('Aggregated') - ) : cell.isRepeatedValue ? null : ( // For cells with repeated values, render null - // Otherwise, just render the regular cell - cell.render('Cell', { editable: true }) - )} - - ) - })} - - )} - )} + {page.map(row => { + prepareRow(row) + return ( + + {row.cells.map(cell => { + return ( + + {cell.isGrouped ? ( + // If it's a grouped cell, add an expander and row count + <> + + {row.isExpanded ? '👇' : '👉'} + {' '} + {cell.render('Cell', { editable: false })} ( + {row.subRows.length}) + + ) : cell.isAggregated ? ( + // If the cell is aggregated, use the Aggregated + // renderer for cell + cell.render('Aggregated') + ) : cell.isRepeatedValue ? null : ( // For cells with repeated values, render null + // Otherwise, just render the regular cell + cell.render('Cell', { editable: true }) + )} + + ) + })} + + ) + })} {/* @@ -580,14 +580,14 @@ function App() { // We need to keep the table from resetting the pageIndex when we // Update data. So we can keep track of that flag with a ref. - const skipPageResetRef = React.useRef(false) + const skipResetRef = React.useRef(false) // When our cell renderer calls updateMyData, we'll use // the rowIndex, columnID and new value to update the // original data const updateMyData = (rowIndex, columnID, value) => { // We also turn on the flag to not reset the page - skipPageResetRef.current = true + skipResetRef.current = true setData(old => old.map((row, index) => { if (index === rowIndex) { @@ -605,14 +605,14 @@ function App() { // so that if data actually changes when we're not // editing it, the page is reset React.useEffect(() => { - skipPageResetRef.current = false + skipResetRef.current = false }, [data]) // Let's add a data resetter/randomizer to help // illustrate that flow... const resetData = () => { // Don't reset the page when we do this - skipPageResetRef.current = true + skipResetRef.current = true setData(originalData) } @@ -623,7 +623,7 @@ function App() { columns={columns} data={data} updateMyData={updateMyData} - disablePageResetOnDataChange={skipPageResetRef.current} + skipReset={skipResetRef.current} /> ) diff --git a/index.d.ts b/index.d.ts index f26ca33..d157ea1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -213,6 +213,7 @@ export type UseExpandedOptions = Partial<{ getSubRows: (row: Row, relativeIndex: number) => Array> manualExpandedKey: IdType paginateExpandedRows: boolean + getResetExpandedDeps: (i: TableInstance) => Array }> export interface UseExpandedHooks { @@ -250,6 +251,7 @@ export type UseFiltersOptions = Partial<{ manualFilters: boolean disableFilters: boolean filterTypes: Filters + getResetFiltersDeps: (i: TableInstance) => Array }> export interface UseFiltersState { @@ -323,6 +325,7 @@ export type UseGroupByOptions = Partial<{ rows: Array>, columnId: IdType ) => Record> + getResetGroupByDeps: (i: TableInstance) => Array }> export interface UseGroupByHooks { @@ -403,7 +406,7 @@ export namespace usePagination { export type UsePaginationOptions = Partial<{ pageCount: number manualPagination: boolean - disablePageResetOnDataChange: boolean + getResetPageDeps: (i: TableInstance) => Array paginateExpandedRows: boolean }> @@ -435,6 +438,7 @@ export namespace useRowSelect { export type UseRowSelectOptions = Partial<{ manualRowSelectedKey: IdType + getResetSelectedRowPathsDeps: (i: TableInstance) => Array }> export interface UseRowSelectHooks { @@ -525,6 +529,7 @@ export type UseSortByOptions = Partial<{ directions: boolean[] ) => Array> sortTypes: Record> + getResetSortByDeps: (i: TableInstance) => Array }> export interface UseSortByHooks { diff --git a/src/plugin-hooks/useExpanded.js b/src/plugin-hooks/useExpanded.js index f63e19f..967ad6b 100755 --- a/src/plugin-hooks/useExpanded.js +++ b/src/plugin-hooks/useExpanded.js @@ -1,6 +1,11 @@ import React from 'react' -import { mergeProps, applyPropHooks, expandRows } from '../utils' +import { + mergeProps, + applyPropHooks, + expandRows, + safeUseLayoutEffect, +} from '../utils' import { addActions, actions } from '../actions' import { defaultState } from '../hooks/useTable' @@ -15,6 +20,8 @@ export const useExpanded = hooks => { useExpanded.pluginName = 'useExpanded' +const defaultGetResetExpandedDeps = instance => [instance.data] + function useMain(instance) { const { debug, @@ -25,8 +32,27 @@ function useMain(instance) { hooks, state: { expanded }, setState, + getResetExpandedDeps = defaultGetResetExpandedDeps, } = instance + // Bypass any effects from firing when this changes + const isMountedRef = React.useRef() + safeUseLayoutEffect(() => { + if (isMountedRef.current) { + setState( + old => ({ + ...old, + expanded: [], + }), + actions.pageChange + ) + } + isMountedRef.current = true + }, [ + setState, + ...(getResetExpandedDeps ? getResetExpandedDeps(instance) : []), + ]) + const toggleExpandedByPath = (path, set) => { const key = path.join('.') diff --git a/src/plugin-hooks/useFilters.js b/src/plugin-hooks/useFilters.js index c2187c2..8978260 100755 --- a/src/plugin-hooks/useFilters.js +++ b/src/plugin-hooks/useFilters.js @@ -1,6 +1,6 @@ import React from 'react' -import { getFirstDefined, isFunction } from '../utils' +import { getFirstDefined, isFunction, safeUseLayoutEffect } from '../utils' import * as filterTypes from '../filterTypes' import { addActions, actions } from '../actions' import { defaultState } from '../hooks/useTable' @@ -27,11 +27,27 @@ function useMain(instance) { disableFilters, state: { filters }, setState, + getResetFiltersDeps = false, } = instance const preFilteredRows = rows const preFilteredFlatRows = flatRows + // Bypass any effects from firing when this changes + const isMountedRef = React.useRef() + safeUseLayoutEffect(() => { + if (isMountedRef.current) { + setState( + old => ({ + ...old, + filters: {}, + }), + actions.pageChange + ) + } + isMountedRef.current = true + }, [setState, ...(getResetFiltersDeps ? getResetFiltersDeps(instance) : [])]) + const setFilter = (id, updater) => { const column = flatColumns.find(d => d.id === id) diff --git a/src/plugin-hooks/usePagination.js b/src/plugin-hooks/usePagination.js index 42d3258..08cdcd1 100755 --- a/src/plugin-hooks/usePagination.js +++ b/src/plugin-hooks/usePagination.js @@ -16,19 +16,24 @@ export const usePagination = hooks => { usePagination.pluginName = 'usePagination' +const defaultGetResetPageDeps = ({ + rows, + manualPagination, + state: { filters, groupBy, sortBy }, +}) => [manualPagination ? null : rows, filters, groupBy, sortBy] + function useMain(instance) { const { - data, rows, manualPagination, - disablePageResetOnDataChange, + getResetPageDeps = defaultGetResetPageDeps, manualExpandedKey = 'expanded', debug, plugins, pageCount: userPageCount, paginateExpandedRows = true, expandSubRows = true, - state: { pageSize, pageIndex, filters, groupBy, sortBy, expanded }, + state: { pageSize, pageIndex, expanded }, setState, } = instance @@ -39,19 +44,10 @@ function useMain(instance) { [] ) - const rowDep = manualPagination ? null : data - - const isPageIndexMountedRef = React.useRef() - // Bypass any effects from firing when this changes - const disablePageResetOnDataChangeRef = React.useRef() - disablePageResetOnDataChangeRef.current = disablePageResetOnDataChange - + const isMountedRef = React.useRef() safeUseLayoutEffect(() => { - if ( - isPageIndexMountedRef.current && - !disablePageResetOnDataChangeRef.current - ) { + if (isMountedRef.current) { setState( old => ({ ...old, @@ -60,8 +56,8 @@ function useMain(instance) { actions.pageChange ) } - isPageIndexMountedRef.current = true - }, [setState, rowDep, filters, groupBy, sortBy]) + isMountedRef.current = true + }, [setState, ...(getResetPageDeps ? getResetPageDeps(instance) : [])]) const pageCount = manualPagination ? userPageCount diff --git a/src/plugin-hooks/useRowSelect.js b/src/plugin-hooks/useRowSelect.js index f02d1c8..d4b790e 100644 --- a/src/plugin-hooks/useRowSelect.js +++ b/src/plugin-hooks/useRowSelect.js @@ -49,14 +49,15 @@ function useRows(rows, instance) { return rows } +const defaultGetResetSelectedRowPathsDeps = ({ rows }) => [rows] + function useMain(instance) { const { hooks, manualRowSelectedKey = 'isSelected', - disableSelectedRowsResetOnDataChange, plugins, flatRows, - data, + getResetSelectedRowPathsDeps = defaultGetResetSelectedRowPathsDeps, state: { selectedRowPaths }, setState, } = instance @@ -78,17 +79,10 @@ function useMain(instance) { } } - const isRowSelectedMountedRef = React.useRef() - // Bypass any effects from firing when this changes - const disableSelectedRowsResetOnDataChangeRef = React.useRef() - disableSelectedRowsResetOnDataChangeRef.current = disableSelectedRowsResetOnDataChange - + const isMountedRef = React.useRef() safeUseLayoutEffect(() => { - if ( - isRowSelectedMountedRef.current && - !disableSelectedRowsResetOnDataChangeRef.current - ) { + if (isMountedRef.current) { setState( old => ({ ...old, @@ -97,8 +91,13 @@ function useMain(instance) { actions.pageChange ) } - isRowSelectedMountedRef.current = true - }, [setState, data]) + isMountedRef.current = true + }, [ + setState, + ...(getResetSelectedRowPathsDeps + ? getResetSelectedRowPathsDeps(instance) + : []), + ]) const toggleRowSelectedAll = set => { setState(old => { diff --git a/src/plugin-hooks/useSortBy.js b/src/plugin-hooks/useSortBy.js index e361ea2..b46f139 100755 --- a/src/plugin-hooks/useSortBy.js +++ b/src/plugin-hooks/useSortBy.js @@ -1,6 +1,6 @@ import React from 'react' -import { ensurePluginOrder, defaultColumn } from '../utils' +import { ensurePluginOrder, defaultColumn, safeUseLayoutEffect } from '../utils' import { addActions, actions } from '../actions' import { defaultState } from '../hooks/useTable' import * as sortTypes from '../sortTypes' @@ -44,12 +44,28 @@ function useMain(instance) { state: { sortBy }, setState, plugins, + getResetSortByDeps = false, } = instance ensurePluginOrder(plugins, ['useFilters'], 'useSortBy', []) // Add custom hooks hooks.getSortByToggleProps = [] + // Bypass any effects from firing when this changes + const isMountedRef = React.useRef() + safeUseLayoutEffect(() => { + if (isMountedRef.current) { + setState( + old => ({ + ...old, + sortBy: [], + }), + actions.pageChange + ) + } + isMountedRef.current = true + }, [setState, ...(getResetSortByDeps ? getResetSortByDeps(instance) : [])]) + // Updates sorting based on a columnID, desc flag and multi flag const toggleSortBy = (columnID, desc, multi) => { return setState(old => { @@ -93,7 +109,8 @@ function useMain(instance) { !hasDescDefined && // Must not be setting desc (multi ? !disableMultiRemove : true) && // If multi, don't allow if disableMultiRemove ((existingSortBy && // Finally, detect if it should indeed be removed - (existingSortBy.desc && !sortDescFirst)) || + existingSortBy.desc && + !sortDescFirst) || (!existingSortBy.desc && sortDescFirst)) ) { action = 'remove'