Call onSelect when a DataSource item changes
Summary: Currently, we call onSelect in DataTable only when user changes their selection. At that moment, we pass the row data to the `onSelect` callback. However, if later the data changes, but the selection stays the same, we do not call `onSelect` again. As result, any listener to onSelect does not receive the latest data. In this diff, we start calling `onSelect` when the selection does not change, but the underlying data does. Reviewed By: mweststrate Differential Revision: D37520346 fbshipit-source-id: a88d34654e9ad0721caf5918dde49b86ba20fc1f
This commit is contained in:
committed by
Facebook GitHub Bot
parent
1052384154
commit
f8763f95fa
@@ -472,7 +472,7 @@ class DataSourceView<T, KeyType> {
|
|||||||
*/
|
*/
|
||||||
public windowEnd = 0;
|
public windowEnd = 0;
|
||||||
|
|
||||||
private outputChangeListener?: (change: OutputChange) => void;
|
private outputChangeListeners = new Set<(change: OutputChange) => void>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the base view data, that is filtered and sorted, but not reversed or windowed
|
* This is the base view data, that is filtered and sorted, but not reversed or windowed
|
||||||
@@ -520,11 +520,11 @@ class DataSourceView<T, KeyType> {
|
|||||||
this.windowEnd = end;
|
this.windowEnd = end;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setListener(listener?: (change: OutputChange) => void) {
|
public addListener(listener: (change: OutputChange) => void) {
|
||||||
if (this.outputChangeListener && listener) {
|
this.outputChangeListeners.add(listener);
|
||||||
console.warn('outputChangeListener already set');
|
return () => {
|
||||||
}
|
this.outputChangeListeners.delete(listener);
|
||||||
this.outputChangeListener = listener;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSortBy(sortBy: undefined | keyof T | ((a: T) => Primitive)) {
|
public setSortBy(sortBy: undefined | keyof T | ((a: T) => Primitive)) {
|
||||||
@@ -617,23 +617,27 @@ class DataSourceView<T, KeyType> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private notifyAllListeners(change: OutputChange) {
|
||||||
|
this.outputChangeListeners.forEach((listener) => listener(change));
|
||||||
|
}
|
||||||
|
|
||||||
private notifyItemUpdated(viewIndex: number) {
|
private notifyItemUpdated(viewIndex: number) {
|
||||||
viewIndex = this.normalizeIndex(viewIndex);
|
viewIndex = this.normalizeIndex(viewIndex);
|
||||||
if (
|
if (
|
||||||
!this.outputChangeListener ||
|
!this.outputChangeListeners.size ||
|
||||||
viewIndex < this.windowStart ||
|
viewIndex < this.windowStart ||
|
||||||
viewIndex >= this.windowEnd
|
viewIndex >= this.windowEnd
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.outputChangeListener({
|
this.notifyAllListeners({
|
||||||
type: 'update',
|
type: 'update',
|
||||||
index: viewIndex,
|
index: viewIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private notifyItemShift(index: number, delta: number) {
|
private notifyItemShift(index: number, delta: number) {
|
||||||
if (!this.outputChangeListener) {
|
if (!this.outputChangeListeners.size) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let viewIndex = this.normalizeIndex(index);
|
let viewIndex = this.normalizeIndex(index);
|
||||||
@@ -641,7 +645,7 @@ class DataSourceView<T, KeyType> {
|
|||||||
viewIndex -= delta; // we need to correct for normalize already using the new length after applying this change
|
viewIndex -= delta; // we need to correct for normalize already using the new length after applying this change
|
||||||
}
|
}
|
||||||
// Idea: we could add an option to automatically shift the window for before events.
|
// Idea: we could add an option to automatically shift the window for before events.
|
||||||
this.outputChangeListener({
|
this.notifyAllListeners({
|
||||||
type: 'shift',
|
type: 'shift',
|
||||||
delta,
|
delta,
|
||||||
index: viewIndex,
|
index: viewIndex,
|
||||||
@@ -656,7 +660,7 @@ class DataSourceView<T, KeyType> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private notifyReset(count: number) {
|
private notifyReset(count: number) {
|
||||||
this.outputChangeListener?.({
|
this.notifyAllListeners({
|
||||||
type: 'reset',
|
type: 'reset',
|
||||||
newCount: count,
|
newCount: count,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export const DataSourceRendererStatic: <T extends object, C>(
|
|||||||
let unmounted = false;
|
let unmounted = false;
|
||||||
|
|
||||||
dataSource.view.setWindow(0, dataSource.limit);
|
dataSource.view.setWindow(0, dataSource.limit);
|
||||||
dataSource.view.setListener((_event) => {
|
const unsubscribe = dataSource.view.addListener((_event) => {
|
||||||
if (unmounted) {
|
if (unmounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ export const DataSourceRendererStatic: <T extends object, C>(
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unmounted = true;
|
unmounted = true;
|
||||||
dataSource.view.setListener(undefined);
|
unsubscribe();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[dataSource, setForceUpdate, useFixedRowHeight],
|
[dataSource, setForceUpdate, useFixedRowHeight],
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ export const DataSourceRendererVirtual: <T extends object, C>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dataSource.view.setListener((event) => {
|
const unsubscribe = dataSource.view.addListener((event) => {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'reset':
|
case 'reset':
|
||||||
rerender(UpdatePrio.HIGH, true);
|
rerender(UpdatePrio.HIGH, true);
|
||||||
@@ -201,7 +201,7 @@ export const DataSourceRendererVirtual: <T extends object, C>(
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unmounted = true;
|
unmounted = true;
|
||||||
dataSource.view.setListener(undefined);
|
unsubscribe();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[dataSource, setForceUpdate, useFixedRowHeight, isUnitTest],
|
[dataSource, setForceUpdate, useFixedRowHeight, isUnitTest],
|
||||||
|
|||||||
@@ -489,9 +489,9 @@ function testEvents<T>(
|
|||||||
): any[] {
|
): any[] {
|
||||||
const ds = createDataSource<T, keyof T>(initial, {key});
|
const ds = createDataSource<T, keyof T>(initial, {key});
|
||||||
const events: any[] = [];
|
const events: any[] = [];
|
||||||
ds.view.setListener((e) => events.push(e));
|
const unsubscribe = ds.view.addListener((e) => events.push(e));
|
||||||
op(ds);
|
op(ds);
|
||||||
ds.view.setListener(undefined);
|
unsubscribe();
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -104,7 +104,8 @@ export function MasterDetail<T extends object>({
|
|||||||
|
|
||||||
// if a tableManagerRef is provided, we piggy back on that same ref
|
// if a tableManagerRef is provided, we piggy back on that same ref
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const tableManagerRef = tableProps.tableManagerRef ?? createRef<undefined | DataTableManager<T>>();
|
const tableManagerRef =
|
||||||
|
tableProps.tableManagerRef ?? createRef<undefined | DataTableManager<T>>();
|
||||||
|
|
||||||
const pausedState = useValue(isPaused, false);
|
const pausedState = useValue(isPaused, false);
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import {debounce} from 'lodash';
|
|||||||
import {useInUnitTest} from '../../utils/useInUnitTest';
|
import {useInUnitTest} from '../../utils/useInUnitTest';
|
||||||
import {createDataSource} from '../../state/createDataSource';
|
import {createDataSource} from '../../state/createDataSource';
|
||||||
import {HighlightProvider} from '../Highlight';
|
import {HighlightProvider} from '../Highlight';
|
||||||
|
import {useLatestRef} from '../../utils/useLatestRef';
|
||||||
|
|
||||||
type DataTableBaseProps<T = any> = {
|
type DataTableBaseProps<T = any> = {
|
||||||
columns: DataTableColumn<T>[];
|
columns: DataTableColumn<T>[];
|
||||||
@@ -179,6 +180,26 @@ export function DataTable<T extends object>(
|
|||||||
|
|
||||||
const {columns, selection, searchValue, sorting} = tableState;
|
const {columns, selection, searchValue, sorting} = tableState;
|
||||||
|
|
||||||
|
const latestSelectionRef = useLatestRef(selection);
|
||||||
|
const latestOnSelectRef = useLatestRef(onSelect);
|
||||||
|
useEffect(() => {
|
||||||
|
if (dataSource) {
|
||||||
|
const unsubscribe = dataSource.view.addListener((change) => {
|
||||||
|
if (
|
||||||
|
change.type === 'update' &&
|
||||||
|
latestSelectionRef.current.items.has(change.index)
|
||||||
|
) {
|
||||||
|
latestOnSelectRef.current?.(
|
||||||
|
getSelectedItem(dataSource, latestSelectionRef.current),
|
||||||
|
getSelectedItems(dataSource, latestSelectionRef.current),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return unsubscribe;
|
||||||
|
}
|
||||||
|
}, [dataSource, latestSelectionRef, latestOnSelectRef]);
|
||||||
|
|
||||||
const visibleColumns = useMemo(
|
const visibleColumns = useMemo(
|
||||||
() => columns.filter((column) => column.visible),
|
() => columns.filter((column) => column.visible),
|
||||||
[columns],
|
[columns],
|
||||||
|
|||||||
@@ -737,6 +737,8 @@ test('selection always has the latest state', () => {
|
|||||||
act(() => {
|
act(() => {
|
||||||
ds.update(2, item3updated);
|
ds.update(2, item3updated);
|
||||||
});
|
});
|
||||||
|
expect(events.splice(0)).toEqual([[item3updated, [item3updated]]]);
|
||||||
|
|
||||||
act(() => {
|
act(() => {
|
||||||
ref.current!.addRangeToSelection(0, 0);
|
ref.current!.addRangeToSelection(0, 0);
|
||||||
});
|
});
|
||||||
@@ -745,5 +747,16 @@ test('selection always has the latest state', () => {
|
|||||||
[item1, [item1, item3updated]], // update reflected in callback!
|
[item1, [item1, item3updated]], // update reflected in callback!
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const item1updated = {
|
||||||
|
title: 'item 1 updated',
|
||||||
|
done: false,
|
||||||
|
};
|
||||||
|
act(() => {
|
||||||
|
ds.update(0, item1updated);
|
||||||
|
});
|
||||||
|
expect(events.splice(0)).toEqual([
|
||||||
|
[item1updated, [item1updated, item3updated]], // update reflected in callback!
|
||||||
|
]);
|
||||||
|
|
||||||
rendering.unmount();
|
rendering.unmount();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user