[change] Add useElementLayout hook to implement onLayout callback

A hook equivalent for implementing onLayout in function components.  Removes
the fallback to using window resize events. A ResizeObserver polyfill is now
requires to use the `onLayout` prop.
This commit is contained in:
Nicolas Gallagher
2020-06-02 10:55:43 -07:00
parent 9dbe17fa6a
commit 51ac083d56
4 changed files with 106 additions and 2 deletions
+1
View File
@@ -45,6 +45,7 @@
"ReactPropsChainableTypeChecker": false,
"ReactPropsCheckType": false,
"ReactPropTypes": false,
"ResizeObserver": false,
"SyntheticEvent": false
},
"rules": {
+83
View File
@@ -0,0 +1,83 @@
/**
* Copyright (c) Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type { ElementRef } from 'react';
import type { LayoutEvent } from '../types';
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import useLayoutEffect from './useLayoutEffect';
import UIManager from '../exports/UIManager';
const DOM_LAYOUT_HANDLER_NAME = '__reactLayoutHandler';
let didWarn = false;
let resizeObserver = null;
function getResizeObserver(): ?ResizeObserver {
if (canUseDOM && typeof window.ResizeObserver !== 'undefined') {
if (resizeObserver == null) {
resizeObserver = new window.ResizeObserver(function(entries) {
entries.forEach(entry => {
const node = entry.target;
const onLayout = node[DOM_LAYOUT_HANDLER_NAME];
if (typeof onLayout === 'function') {
// We still need to measure the view because browsers don't yet provide
// border-box dimensions in the entry
UIManager.measure(node, (x, y, width, height, left, top) => {
const event: LayoutEvent = {
// $FlowFixMe
nativeEvent: {
layout: { x, y, width, height, left, top }
},
timeStamp: Date.now()
};
Object.defineProperty(event.nativeEvent, 'target', {
enumerable: true,
get: () => entry.target
});
onLayout(event);
});
}
});
});
}
} else if (!didWarn) {
if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
console.warn(
'onLayout relies on ResizeObserver which is not supported by your browser. ' +
'Please include a polyfill, e.g., https://github.com/que-etc/resize-observer-polyfill.'
);
didWarn = true;
}
}
return resizeObserver;
}
export default function useElementLayout(
ref: ElementRef<any>,
onLayout?: (e: LayoutEvent) => void
) {
const observer = getResizeObserver();
useLayoutEffect(() => {
const node = ref.current;
if (node != null && observer != null && typeof onLayout === 'function') {
observer.observe(node);
// $FlowFixMe
node[DOM_LAYOUT_HANDLER_NAME] = onLayout;
}
return () => {
if (node != null && observer != null) {
observer.unobserve(node);
// $FlowFixMe
delete node[DOM_LAYOUT_HANDLER_NAME];
}
};
}, [ref, onLayout, observer]);
}
+18
View File
@@ -0,0 +1,18 @@
/**
* Copyright (c) Nicolas Gallagher.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* useLayoutEffect throws an error on the server. On the few occasions where is
* problematic, use this hook.
*
* @flow
*/
import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import { useEffect, useLayoutEffect } from 'react';
const useLayoutEffectImpl: typeof useLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;
export default useLayoutEffectImpl;
+4 -2
View File
@@ -35,8 +35,10 @@ export type LayoutValue = {
export type LayoutEvent = {
nativeEvent: {
layout: LayoutValue
}
layout: LayoutValue,
target: any
},
timeStamp: number
};
export type PointValue = {|