Skip to content

Commit

Permalink
Create new ParentSizeModern component.
Browse files Browse the repository at this point in the history
This component is a clone of `ParentSize` that doesn't use a
`ResizeObserver` polyfill.
  • Loading branch information
koddsson committed Nov 26, 2020
1 parent 293d5fe commit 0a3ecac
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 4 deletions.
4 changes: 4 additions & 0 deletions packages/visx-responsive/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ let chartToRender = (
// ... Render the chartToRender somewhere
```

##### ⚠️ `ResizeObserver` dependency

If you don't need a polyfill for `ResizeObserver` or are already including it in your bundle, you should use `ParentSizeModern` which doesn't include the polyfill in the component.

## Installation

```
Expand Down
1 change: 1 addition & 0 deletions packages/visx-responsive/src/components/ParentSize.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import debounce from 'lodash/debounce';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import ResizeObserver from 'resize-observer-polyfill';

export type ParentSizeProps = {
/** Optional `className` to add to the parent `div` wrapper used for size measurement. */
Expand Down
92 changes: 92 additions & 0 deletions packages/visx-responsive/src/components/ParentSizeModern.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import debounce from 'lodash/debounce';
import React, { useEffect, useMemo, useRef, useState } from 'react';

export type ParentSizeProps = {
/** Optional `className` to add to the parent `div` wrapper used for size measurement. */
className?: string;
/** Child render updates upon resize are delayed until `debounceTime` milliseconds _after_ the last resize event is observed. */
debounceTime?: number;
/** Optional flag to toggle leading debounce calls. When set to true this will ensure that the component always renders immediately. (defaults to true) */
enableDebounceLeadingCall?: boolean;
/** Optional dimensions provided won't trigger a state change when changed. */
ignoreDimensions?: keyof ParentSizeState | (keyof ParentSizeState)[];
/** Optional `style` object to apply to the parent `div` wrapper used for size measurement. */
parentSizeStyles?: React.CSSProperties;
/** Child render function `({ width, height, top, left, ref, resize }) => ReactNode`. */
children: (
args: {
ref: HTMLDivElement | null;
resize: (state: ParentSizeState) => void;
} & ParentSizeState,
) => React.ReactNode;
};

type ParentSizeState = {
width: number;
height: number;
top: number;
left: number;
};

export type ParentSizeProvidedProps = ParentSizeState;

export default function ParentSize({
className,
children,
debounceTime = 300,
ignoreDimensions = [],
parentSizeStyles = { width: '100%', height: '100%' },
enableDebounceLeadingCall = true,
...restProps
}: ParentSizeProps & Omit<JSX.IntrinsicElements['div'], keyof ParentSizeProps>) {
const target = useRef<HTMLDivElement | null>(null);
const animationFrameID = useRef(0);

const [state, setState] = useState<ParentSizeState>({ width: 0, height: 0, top: 0, left: 0 });

const resize = useMemo(() => {
const normalized = Array.isArray(ignoreDimensions) ? ignoreDimensions : [ignoreDimensions];

return debounce(
(incoming: ParentSizeState) => {
setState(existing => {
const stateKeys = Object.keys(existing) as (keyof ParentSizeState)[];
const keysWithChanges = stateKeys.filter(key => existing[key] !== incoming[key]);
const shouldBail = keysWithChanges.every(key => normalized.includes(key));

return shouldBail ? existing : incoming;
});
},
debounceTime,
{ leading: enableDebounceLeadingCall },
);
}, [debounceTime, enableDebounceLeadingCall, ignoreDimensions]);

useEffect(() => {
const observer = new ResizeObserver((entries = [] /** , observer */) => {
entries.forEach(entry => {
const { left, top, width, height } = entry.contentRect;
animationFrameID.current = window.requestAnimationFrame(() => {
resize({ width, height, top, left });
});
});
});
if (target.current) observer.observe(target.current);

return () => {
window.cancelAnimationFrame(animationFrameID.current);
observer.disconnect();
resize.cancel();
};
}, [resize]);

return (
<div style={parentSizeStyles} ref={target} className={className} {...restProps}>
{children({
...state,
ref: target.current,
resize,
})}
</div>
);
}
9 changes: 5 additions & 4 deletions packages/visx-responsive/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { default as ScaleSVG } from './components/ScaleSVG';
export { default as ParentSize } from './components/ParentSize';
export { default as withParentSize } from './enhancers/withParentSize';
export { default as withScreenSize } from './enhancers/withScreenSize';
export { default as ScaleSVG } from "./components/ScaleSVG";
export { default as ParentSize } from "./components/ParentSize";
export { default as ParentSizeModern } from "./components/ParentSizeModern";
export { default as withParentSize } from "./enhancers/withParentSize";
export { default as withScreenSize } from "./enhancers/withScreenSize";

0 comments on commit 0a3ecac

Please sign in to comment.