Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Improve sizing logic #350

Merged
merged 9 commits into from
Oct 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions packages/grid/_modules_/grid/GridComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import {
useSelection,
useSorting,
} from './hooks/features';
import { DEFAULT_GRID_OPTIONS, ElementSize, RootContainerRef } from './models';
import { ElementSize, RootContainerRef } from './models';
import { COMPONENT_ERROR, DATA_CONTAINER_CSS_CLASS } from './constants';
import { GridRoot } from './components/styled-wrappers/GridRoot';
import { GridDataContainer } from './components/styled-wrappers/GridDataContainer';
import { GridColumnsContainer } from './components/styled-wrappers/GridColumnsContainer';
import { useVirtualRows } from './hooks/virtualization';
import {
ApiContext,
AutoSizerWrapper,
AutoSizer,
ColumnsHeader,
DefaultFooter,
OptionsContext,
Expand All @@ -43,11 +43,8 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
ref,
) {
const [internalOptions, setInternalOptions] = useOptionsProp(props);
useLoggerFactory(
internalOptions?.logger,
internalOptions?.logLevel || DEFAULT_GRID_OPTIONS.logLevel,
);
const gridLogger = useLogger('Grid');
useLoggerFactory(internalOptions?.logger, internalOptions?.logLevel);
const gridLogger = useLogger('Material-UI Data Grid');

const rootContainerRef: RootContainerRef = React.useRef<HTMLDivElement>(null);
const handleRef = useForkRef(rootContainerRef, ref);
Expand All @@ -66,7 +63,7 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
internalApiRef,
]);

const initialised = useApi(rootContainerRef, internalOptions, apiRef);
const initialised = useApi(rootContainerRef, apiRef);

const errorHandler = (args: any) => {
// We are handling error here, to set up the handler as early as possible and be able to catch error thrown at init time.
Expand Down Expand Up @@ -119,6 +116,19 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps

const onResize = React.useCallback(
(size: ElementSize) => {
if (size.height === 0) {
gridLogger.warn(
[
'The parent of the grid has an empty height.',
'You need to make sure the container has an intrinsic height.',
'The grid displays with a height of 0px.',
'',
'You can find a solution in the docs:',
'https://material-ui.com/components/data-grid/rendering/#layout',
].join('\n'),
);
}

gridLogger.info('resized...', size);
apiRef!.current.resize();
},
Expand Down Expand Up @@ -161,7 +171,7 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
);

return (
<AutoSizerWrapper onResize={debouncedOnResize} style={{ height: 'unset', width: 'unset' }}>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix is to remove style={{ height: 'unset', width: 'unset' }}

<AutoSizer onResize={debouncedOnResize}>
{(size: any) => (
<GridRoot
ref={handleRef}
Expand Down Expand Up @@ -258,6 +268,6 @@ export const GridComponent = React.forwardRef<HTMLDivElement, GridComponentProps
</ErrorBoundary>
</GridRoot>
)}
</AutoSizerWrapper>
</AutoSizer>
);
});
159 changes: 159 additions & 0 deletions packages/grid/_modules_/grid/components/AutoSizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import * as React from 'react';
import { useForkRef, ownerWindow, useEventCallback } from '@material-ui/core/utils';
import createDetectElementResize from '../lib/createDetectElementResize';
// TODO replace with https://caniuse.com/resizeobserver.

export interface AutoSizerSize {
height: number;
width: number;
}

// Credit to https://github.com/bvaughn/react-virtualized/blob/master/source/AutoSizer/AutoSizer.js
// for the sources.

export interface AutoSizerProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
/**
* Function responsible for rendering children.
*/
children: (size: AutoSizerSize) => React.ReactNode;
/**
* Default height to use for initial render; useful for SSR.
* @default 0
*/
defaultHeight?: number;
/**
* Default width to use for initial render; useful for SSR.
* @default 0
*/
defaultWidth?: number;
/**
* If `true`, disable dynamic :height property.
* @default false
*/
disableHeight?: boolean;
/**
* If `true`, disable dynamic :width property.
* @default false
*/
disableWidth?: boolean;
/**
* Nonce of the inlined stylesheet for Content Security Policy.
*/
nonce?: string;
/**
* Callback to be invoked on-resize.
*/
onResize?: (size: AutoSizerSize) => void;
}

// TODO v5: replace with @material-ui/core/utils/useEnhancedEffect.
const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;

export const AutoSizer = React.forwardRef<HTMLDivElement, AutoSizerProps>(function AutoSizer(
props,
ref,
) {
const {
children,
defaultHeight = 0,
defaultWidth = 0,
disableHeight = false,
disableWidth = false,
nonce,
onResize,
style,
...other
} = props;

const [state, setState] = React.useState({
height: defaultHeight,
width: defaultWidth,
});

const rootRef = React.useRef<HTMLDivElement>(null);
const parentElement = React.useRef(null) as React.MutableRefObject<HTMLElement | null>;

const handleResize = useEventCallback(() => {
// Guard against AutoSizer component being removed from the DOM immediately after being added.
// This can result in invalid style values which can result in NaN values if we don't handle them.
// See issue #150 for more context.
if (parentElement.current) {
const height = parentElement.current.offsetHeight || 0;
const width = parentElement.current.offsetWidth || 0;

const win = ownerWindow(parentElement.current);
const computedStyle = win.getComputedStyle(parentElement.current);
const paddingLeft = parseInt(computedStyle.paddingLeft, 10) || 0;
const paddingRight = parseInt(computedStyle.paddingRight, 10) || 0;
const paddingTop = parseInt(computedStyle.paddingTop, 10) || 0;
const paddingBottom = parseInt(computedStyle.paddingBottom, 10) || 0;

const newHeight = height - paddingTop - paddingBottom;
const newWidth = width - paddingLeft - paddingRight;

if (
(!disableHeight && state.height !== newHeight) ||
(!disableWidth && state.width !== newWidth)
) {
setState({
height: height - paddingTop - paddingBottom,
width: width - paddingLeft - paddingRight,
});

if (onResize) {
onResize({ height, width });
}
}
}
});

useEnhancedEffect(() => {
parentElement.current = rootRef.current!.parentElement;

if (!parentElement) {
return undefined;
}

const win = ownerWindow(parentElement.current ?? undefined);

const detectElementResize = createDetectElementResize(nonce, win);
detectElementResize.addResizeListener(parentElement.current, handleResize);
// @ts-expect-error fixed in v5
handleResize();

return () => {
detectElementResize.removeResizeListener(parentElement.current, handleResize);
};
}, [nonce, handleResize]);

// Outer div should not force width/height since that may prevent containers from shrinking.
// Inner component should overflow and use calculated width/height.
// See issue #68 for more information.
const outerStyle: any = { overflow: 'visible' };
const childParams: any = {};

if (!disableHeight) {
outerStyle.height = 0;
childParams.height = state.height;
}

if (!disableWidth) {
outerStyle.width = 0;
childParams.width = state.width;
}

const handleRef = useForkRef(rootRef, ref);

return (
<div
ref={handleRef}
style={{
...outerStyle,
...style,
}}
{...other}
>
{state.height === 0 && state.width === 0 ? null : children(childParams)}
</div>
);
});
3 changes: 0 additions & 3 deletions packages/grid/_modules_/grid/components/autosizer.tsx

This file was deleted.

15 changes: 7 additions & 8 deletions packages/grid/_modules_/grid/components/default-footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ export interface DefaultFooterProps {
}

export const DefaultFooter = React.forwardRef<HTMLDivElement, DefaultFooterProps>(
function DefaultFooter({ options, rowCount, paginationComponent }, ref) {
const api = React.useContext(ApiContext);
function DefaultFooter(props, ref) {
const { options, rowCount, paginationComponent } = props;
const apiRef = React.useContext(ApiContext);
const [selectedRowCount, setSelectedCount] = React.useState(0);

React.useEffect(() => {
if (api && api.current) {
return api.current!.onSelectionChange(({ rows }) => setSelectedCount(rows.length));
}

return undefined;
}, [api]);
return apiRef!.current.onSelectionChange(({ rows }) => {
setSelectedCount(rows.length);
});
}, [apiRef]);

if (options.hideFooter) {
return null;
Expand Down
2 changes: 1 addition & 1 deletion packages/grid/_modules_/grid/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './api-context';
export * from './autosizer';
export * from './AutoSizer';
export * from './cell';
export * from './checkbox-renderer';
export * from './column-header-item';
Expand Down
8 changes: 2 additions & 6 deletions packages/grid/_modules_/grid/hooks/root/useApi.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import * as React from 'react';
import { useLogger } from '../utils/useLogger';
import { COMPONENT_ERROR, UNMOUNT } from '../../constants/eventsConstants';
import { GridOptions, ApiRef } from '../../models';
import { ApiRef } from '../../models';
import { useApiMethod } from './useApiMethod';

export function useApi(
gridRootRef: React.RefObject<HTMLDivElement>,
options: GridOptions,
apiRef: ApiRef,
): boolean {
export function useApi(gridRootRef: React.RefObject<HTMLDivElement>, apiRef: ApiRef): boolean {
const [initialised, setInit] = React.useState(false);
const logger = useLogger('useApi');

Expand Down
5 changes: 3 additions & 2 deletions packages/grid/_modules_/grid/hooks/utils/useLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ function getAppender(name: string, logLevel: string, appender: Logger = console)
if (idx >= minLogLevelIdx) {
loggerObj[method] = (...args: any[]) => {
const [message, ...rest] = args;
(appender as any)[method](`[${name}] - ${message}`, ...rest);

(appender as any)[method](`${name}: ${message}`, ...rest);
};
} else {
loggerObj[method] = noop;
Expand All @@ -68,7 +69,7 @@ let factory: LoggerFactoryFn | null;

export function useLoggerFactory(
customLogger?: Logger | LoggerFactoryFn,
logLevel: string | boolean = 'debug',
logLevel: string | boolean = process.env.NODE_ENV === 'production' ? 'error' : 'warn',
) {
if (forceDebug) {
factory = defaultFactory('debug');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ import { useApiEventHandler } from '../root/useApiEventHandler';
type UseVirtualRowsReturnType = Partial<RenderContextProps> | null;

// TODO v5: replace with @material-ui/core/utils/useEnhancedEffect.
const useEnhancedEffect =
typeof window !== 'undefined' && process.env.NODE_ENV !== 'test'
? React.useLayoutEffect
: React.useEffect;
const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved

export const useVirtualRows = (
colRef: React.MutableRefObject<HTMLDivElement | null>,
Expand Down
52 changes: 0 additions & 52 deletions packages/grid/_modules_/grid/lib/autosizer/types.d.ts

This file was deleted.

Loading