useVirtualizedGrid.tsx 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import type {DependencyList, RefObject} from 'react';
  2. import {useCallback, useEffect, useMemo, useState} from 'react';
  3. import type {CellMeasurerCacheParams, MultiGrid} from 'react-virtualized';
  4. import {CellMeasurerCache} from 'react-virtualized';
  5. type Opts = {
  6. /**
  7. * Options for the CellMeasurerCache constructor
  8. */
  9. cellMeasurer: CellMeasurerCacheParams;
  10. /**
  11. * How many columns are being rendered
  12. */
  13. columnCount: number;
  14. /**
  15. * List of other values that should trigger re-computing column sizes
  16. */
  17. deps: DependencyList;
  18. /**
  19. * There must be one column with a dynamic width, so the table can fill all available width inside the container
  20. */
  21. dynamicColumnIndex: number;
  22. /**
  23. * The <MultiGrid> elem.
  24. */
  25. gridRef: RefObject<MultiGrid>;
  26. };
  27. const globalCellMeasurerCache = new WeakMap<CellMeasurerCacheParams, CellMeasurerCache>();
  28. function useVirtualizedGrid({
  29. cellMeasurer,
  30. columnCount,
  31. deps,
  32. dynamicColumnIndex,
  33. gridRef,
  34. }: Opts) {
  35. const cache = useMemo(() => {
  36. if (globalCellMeasurerCache.has(cellMeasurer)) {
  37. return globalCellMeasurerCache.get(cellMeasurer)!;
  38. }
  39. const newCellMeasurer = new CellMeasurerCache(cellMeasurer);
  40. globalCellMeasurerCache.set(cellMeasurer, newCellMeasurer);
  41. return newCellMeasurer;
  42. }, [cellMeasurer]);
  43. const [scrollBarWidth, setScrollBarWidth] = useState(0);
  44. const onWrapperResize = useCallback(() => {
  45. // TODO: debounce?
  46. gridRef.current?.recomputeGridSize({columnIndex: dynamicColumnIndex});
  47. }, [gridRef, dynamicColumnIndex]);
  48. // Recompute the width of the dynamic column when deps change (ie: a search/filter is applied)
  49. useEffect(onWrapperResize, [onWrapperResize, deps]);
  50. const onScrollbarPresenceChange = useCallback(({vertical, size}) => {
  51. setScrollBarWidth(vertical ? size : 0);
  52. }, []);
  53. const getColumnWidth = useCallback(
  54. (width: number) =>
  55. ({index}) => {
  56. if (index !== dynamicColumnIndex) {
  57. return cache.columnWidth({index});
  58. }
  59. const columns = Array.from(new Array(columnCount));
  60. const fullWidth = width - scrollBarWidth;
  61. // Take the full width available, and remove all the static/cached widths
  62. // so we know how much space is available for our dynamic column.
  63. const colWidth = columns.reduce(
  64. (remainingWidth, _, i) =>
  65. i === dynamicColumnIndex
  66. ? remainingWidth
  67. : remainingWidth - cache.columnWidth({index: i}),
  68. fullWidth
  69. );
  70. return Math.max(colWidth, 200);
  71. },
  72. [cache, columnCount, dynamicColumnIndex, scrollBarWidth]
  73. );
  74. return {
  75. cache,
  76. getColumnWidth,
  77. onScrollbarPresenceChange,
  78. onWrapperResize,
  79. };
  80. }
  81. export default useVirtualizedGrid;