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

[RNMobile] Create cross platform useResizeObserver() hook #19918

Merged
merged 12 commits into from
Mar 13, 2020
Merged
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,8 @@

exports[`core/embed block edit matches snapshot 1`] = `
<div
class="components-placeholder wp-block-embed"
class="components-placeholder wp-block-embed is-large"
>
<iframe
aria-hidden="true"
aria-label="resize-listener"
frameborder="0"
src="about:blank"
style="display:block;opacity:0;position:absolute;top:0;left:0;height:100%;width:100%;overflow:hidden;pointer-events:none;z-index:-1"
tabindex="-1"
/>
Comment on lines -5 to -14
Copy link
Member

Choose a reason for hiding this comment

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

These snapshot changes do not seem like they are sensible, since they undo the fixes intended with #19825.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see that you updated the mocking in the tests, so that a value is provided. So maybe they are sensible. I'll take a closer look at those changes.

<div
class="components-placeholder__label"
>
Expand Down
1 change: 0 additions & 1 deletion packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
"moment": "^2.22.1",
"re-resizable": "^6.0.0",
"react-dates": "^17.1.1",
"react-resize-aware": "^3.0.0",
"react-spring": "^8.0.20",
"reakit": "^1.0.0-beta.12",
"rememo": "^3.0.0",
Expand Down
10 changes: 7 additions & 3 deletions packages/components/src/placeholder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
* External dependencies
*/
import classnames from 'classnames';
import useResizeAware from 'react-resize-aware';

/**
* WordPress dependencies
*/
import { useResizeObserver } from '@wordpress/compose';

/**
* Internal dependencies
Expand All @@ -26,9 +30,9 @@ function Placeholder( {
isColumnLayout,
...additionalProps
} ) {
const [ resizeListener, { width } ] = useResizeAware();
const [ resizeListener, { width } ] = useResizeObserver();

// Since `useResizeAware` will report a width of `null` until after the
// Since `useResizeObserver` will report a width of `null` until after the
// first render, avoid applying any modifier classes until width is known.
let modifierClassNames;
if ( typeof width === 'number' ) {
Expand Down
15 changes: 8 additions & 7 deletions packages/components/src/placeholder/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@
* External dependencies
*/
import { shallow } from 'enzyme';
import useResizeAware from 'react-resize-aware';

/**
* WordPress dependencies
*/
import { more } from '@wordpress/icons';
import { useResizeObserver } from '@wordpress/compose';

/**
* Internal dependencies
*/
import Placeholder from '../';

jest.mock( 'react-resize-aware' );

describe( 'Placeholder', () => {
beforeEach( () => {
useResizeAware.mockReturnValue( [ <div key="1" />, { width: 320 } ] );
useResizeObserver.mockReturnValue( [
<div key="1" />,
{ width: 320 },
] );
} );

describe( 'basic rendering', () => {
Expand Down Expand Up @@ -109,8 +110,8 @@ describe( 'Placeholder', () => {
} );

describe( 'resize aware', () => {
it( 'should not assign modifier class in first-pass `null` width from `useResizeAware`', () => {
useResizeAware.mockReturnValue( [
it( 'should not assign modifier class in first-pass `null` width from `useResizeObserver`', () => {
useResizeObserver.mockReturnValue( [
<div key="1" />,
{ width: 320 },
] );
Expand All @@ -123,7 +124,7 @@ describe( 'Placeholder', () => {
} );

it( 'should assign modifier class', () => {
useResizeAware.mockReturnValue( [
useResizeObserver.mockReturnValue( [
<div key="1" />,
{ width: null },
] );
Expand Down
24 changes: 24 additions & 0 deletions packages/compose/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,30 @@ _Returns_

- `boolean`: Reduced motion preference value.

<a name="useResizeObserver" href="#useResizeObserver">#</a> **useResizeObserver**

Hook which allows to listen the resize event of any target element when it changes sizes.
_Note: `useResizeObserver` will report `null` until after first render_

_Usage_

```js
const App = () => {
const [ resizeListener, sizes ] = useResizeObserver();

return (
<div>
{ resizeListener }
Your content here
</div>
);
};
```

_Returns_

- `?Object`: Measurements object with properties `width` and `height`.

<a name="useViewportMatch" href="#useViewportMatch">#</a> **useViewportMatch**

Returns true if the viewport matches the given query, or false otherwise.
Expand Down
3 changes: 2 additions & 1 deletion packages/compose/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"@wordpress/element": "file:../element",
"@wordpress/is-shallow-equal": "file:../is-shallow-equal",
"lodash": "^4.17.15",
"mousetrap": "^1.6.2"
"mousetrap": "^1.6.2",
"react-resize-aware": "^3.0.0"
},
"publishConfig": {
"access": "public"
Expand Down
34 changes: 34 additions & 0 deletions packages/compose/src/hooks/use-resize-observer/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* External dependencies
*/
import useResizeAware from 'react-resize-aware';

/**
* Hook which allows to listen the resize event of any target element when it changes sizes.
* _Note: `useResizeObserver` will report `null` until after first render_
*
* @return {?Object} Measurements object with properties `width` and `height`.
lukewalczak marked this conversation as resolved.
Show resolved Hide resolved
*
* @example
*
* ```js
* const App = () => {
* const [ resizeListener, sizes ] = useResizeObserver();
*
* return (
* <div>
* { resizeListener }
* Your content here
* </div>
* );
* };
* ```
*
*/
const useResizeObserver = () => {
const [ resizeListener, sizes ] = useResizeAware();

return [ resizeListener, sizes ];
};

export default useResizeObserver;
lukewalczak marked this conversation as resolved.
Show resolved Hide resolved
56 changes: 56 additions & 0 deletions packages/compose/src/hooks/use-resize-observer/index.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* External dependencies
*/
import { View, StyleSheet } from 'react-native';
/**
* WordPress dependencies
*/
import { useState, useCallback } from '@wordpress/element';

/**
* Hook which allows to listen the resize event of any target element when it changes sizes.
*
* @return {Object} Measurements object with properties `width` and `height`.
lukewalczak marked this conversation as resolved.
Show resolved Hide resolved
*
* @example
*
* ```js
* const App = () => {
* const [ resizeListener, sizes ] = useResizeObserver();
*
* return (
* <View>
* { resizeListener }
* Your content here
* </View>
* );
* };
* ```
*
*/
const useResizeObserver = () => {
const [ measurements, setMeasurements ] = useState( null );

const onLayout = useCallback( ( { nativeEvent } ) => {
const { width, height } = nativeEvent.layout;
setMeasurements( ( prevState ) => {
if (
! prevState ||
prevState.width !== width ||
prevState.height !== height
) {
return { width, height };
}
return prevState;
} );
}, [] );
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this include setMeasurements in the hook dependencies?

Copy link
Member Author

Choose a reason for hiding this comment

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

I've tested it and looks like setMeasurements is not necessary since it doesn't change - it's called only once onLayout changed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I didn't know this:

React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list

https://reactjs.org/docs/hooks-reference.html#usestate


return [
// `key` is needless in that place since we are not rendering an array of elements
// eslint-disable-next-line react/jsx-key
lukewalczak marked this conversation as resolved.
Show resolved Hide resolved
<View style={ StyleSheet.absoluteFill } onLayout={ onLayout } />,
lukewalczak marked this conversation as resolved.
Show resolved Hide resolved
measurements,
];
};

export default useResizeObserver;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* External dependencies
*/
import { create, act } from 'react-test-renderer';
import { View } from 'react-native';

/**
* Internal dependencies
*/
import useResizeObserver from '../';

const TestComponent = ( { onLayout } ) => {
const [ resizeObserver, sizes ] = useResizeObserver();

return (
<View sizes={ sizes } onLayout={ onLayout }>
{ resizeObserver }
</View>
);
};

const renderWithOnLayout = ( component ) => {
const testComponent = create( component );

const mockNativeEvent = {
nativeEvent: {
layout: {
width: 300,
height: 500,
},
},
};

act( () => {
testComponent.toJSON().children[ 0 ].props.onLayout( mockNativeEvent );
} );

return testComponent.toJSON();
};

describe( 'useResizeObserver()', () => {
it( 'should return "{ width: 300, height: 500 }"', () => {
const component = renderWithOnLayout( <TestComponent /> );

expect( component.props.sizes ).toMatchObject( {
width: 300,
height: 500,
} );
} );
} );
1 change: 1 addition & 0 deletions packages/compose/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ export { default as useKeyboardShortcut } from './hooks/use-keyboard-shortcut';
export { default as useMediaQuery } from './hooks/use-media-query';
export { default as useReducedMotion } from './hooks/use-reduced-motion';
export { default as useViewportMatch } from './hooks/use-viewport-match';
export { default as useResizeObserver } from './hooks/use-resize-observer';
1 change: 1 addition & 0 deletions packages/compose/src/index.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { default as withPreferredColorScheme } from './higher-order/with-preferr

// Hooks
export { default as usePreferredColorScheme } from './hooks/use-preferred-color-scheme';
export { default as useResizeObserver } from './hooks/use-resize-observer';
23 changes: 1 addition & 22 deletions storybook/test/__snapshots__/index.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4311,29 +4311,8 @@ exports[`Storyshots Components/Panel With Icon 1`] = `

exports[`Storyshots Components/Placeholder Default 1`] = `
<div
className="components-placeholder"
className="components-placeholder is-large"
>
<iframe
aria-hidden={true}
aria-label="resize-listener"
frameBorder={0}
src="about:blank"
style={
Object {
"display": "block",
"height": "100%",
"left": 0,
"opacity": 0,
"overflow": "hidden",
"pointerEvents": "none",
"position": "absolute",
"top": 0,
"width": "100%",
"zIndex": -1,
}
}
tabIndex={-1}
/>
<div
className="components-placeholder__label"
>
Expand Down
5 changes: 5 additions & 0 deletions test/unit/config/global-mocks.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
jest.mock( '@wordpress/compose', () => {
const App = () => null;
return {
...jest.requireActual( '@wordpress/compose' ),
useViewportMatch: jest.fn(),
useResizeObserver: jest.fn( () => [
<App key={ 'mock-key' } />,
{ width: 700, height: 500 },
] ),
};
} );