From df1b62cd454565cc3d07662f84848b483c6f8744 Mon Sep 17 00:00:00 2001 From: Maxime Thirouin Date: Tue, 19 Nov 2019 23:31:59 +0100 Subject: [PATCH] [add] useWindowDimensions hook Add the 'useWindowDimensions' hook from React Native Close #1487 --- .../src/moduleMap.js | 3 +- .../src/exports/DeviceInfo/index.js | 6 ++- .../src/exports/Dimensions/index.js | 38 +++++++++++++++---- .../src/exports/useWindowDimensions/index.js | 35 +++++++++++++++++ packages/react-native-web/src/index.js | 7 +++- 5 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 packages/react-native-web/src/exports/useWindowDimensions/index.js diff --git a/packages/babel-plugin-react-native-web/src/moduleMap.js b/packages/babel-plugin-react-native-web/src/moduleMap.js index 0ef43d9c7..5bc0ee6c7 100644 --- a/packages/babel-plugin-react-native-web/src/moduleMap.js +++ b/packages/babel-plugin-react-native-web/src/moduleMap.js @@ -64,5 +64,6 @@ module.exports = { findNodeHandle: true, processColor: true, render: true, - unmountComponentAtNode: true + unmountComponentAtNode: true, + useWindowDimensions: true }; diff --git a/packages/react-native-web/src/exports/DeviceInfo/index.js b/packages/react-native-web/src/exports/DeviceInfo/index.js index b743c1cfb..6961968e5 100644 --- a/packages/react-native-web/src/exports/DeviceInfo/index.js +++ b/packages/react-native-web/src/exports/DeviceInfo/index.js @@ -7,12 +7,14 @@ * @flow */ +import type { DisplayMetrics } from '../Dimensions'; + import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; import Dimensions from '../Dimensions'; const DeviceInfo = { Dimensions: { - get windowPhysicalPixels() { + get windowPhysicalPixels(): DisplayMetrics { const { width, height, fontScale, scale } = Dimensions.get('window'); return { width: width * scale, @@ -21,7 +23,7 @@ const DeviceInfo = { fontScale }; }, - get screenPhysicalPixels() { + get screenPhysicalPixels(): DisplayMetrics { const { width, height, fontScale, scale } = Dimensions.get('screen'); return { width: width * scale, diff --git a/packages/react-native-web/src/exports/Dimensions/index.js b/packages/react-native-web/src/exports/Dimensions/index.js index 0ae3ba043..63cb13e2c 100644 --- a/packages/react-native-web/src/exports/Dimensions/index.js +++ b/packages/react-native-web/src/exports/Dimensions/index.js @@ -12,15 +12,31 @@ import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment'; import debounce from 'debounce'; import invariant from 'fbjs/lib/invariant'; +export type DisplayMetrics = {| + fontScale: number, + height: number, + scale: number, + width: number +|}; + +type DimensionsValue = {| + window?: DisplayMetrics, + screen?: DisplayMetrics +|}; + +type DimensionKey = 'window' | 'screen'; + +type DimensionEventListenerType = 'change'; + const win = canUseDOM ? window : { devicePixelRatio: undefined, - innerHeight: undefined, - innerWidth: undefined, + innerHeight: (undefined: any), + innerWidth: (undefined: any), screen: { - height: undefined, - width: undefined + height: (undefined: any), + width: (undefined: any) } }; @@ -28,12 +44,12 @@ const dimensions = {}; const listeners = {}; export default class Dimensions { - static get(dimension: string): Object { + static get(dimension: DimensionKey): DisplayMetrics { invariant(dimensions[dimension], `No dimension set for key ${dimension}`); return dimensions[dimension]; } - static set(initialDimensions: ?{ [key: string]: any }): void { + static set(initialDimensions: ?DimensionsValue): void { if (initialDimensions) { if (canUseDOM) { invariant(false, 'Dimensions cannot be set in the browser'); @@ -64,12 +80,18 @@ export default class Dimensions { } } - static addEventListener(type: string, handler: Function): void { + static addEventListener( + type: DimensionEventListenerType, + handler: DimensionsValue => void + ): void { listeners[type] = listeners[type] || []; listeners[type].push(handler); } - static removeEventListener(type: string, handler: Function): void { + static removeEventListener( + type: DimensionEventListenerType, + handler: DimensionsValue => void + ): void { if (Array.isArray(listeners[type])) { listeners[type] = listeners[type].filter(_handler => _handler !== handler); } diff --git a/packages/react-native-web/src/exports/useWindowDimensions/index.js b/packages/react-native-web/src/exports/useWindowDimensions/index.js new file mode 100644 index 000000000..a18aff597 --- /dev/null +++ b/packages/react-native-web/src/exports/useWindowDimensions/index.js @@ -0,0 +1,35 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + * @flow strict-local + */ + +'use strict'; + +import type { DisplayMetrics } from '../Dimensions'; + +import Dimensions from '../Dimensions'; +import { useEffect, useState } from 'react'; + +export default function useWindowDimensions(): DisplayMetrics { + const [dims, setDims] = useState(() => Dimensions.get('window')); + useEffect(() => { + function handleChange({ window }) { + // $FlowFixMe + setDims(window); + } + Dimensions.addEventListener('change', handleChange); + // We might have missed an update between calling `get` in render and + // `addEventListener` in this handler, so we set it here. If there was + // no change, React will filter out this update as a no-op. + setDims(Dimensions.get('window')); + return () => { + Dimensions.removeEventListener('change', handleChange); + }; + }, []); + return dims; +} diff --git a/packages/react-native-web/src/index.js b/packages/react-native-web/src/index.js index 371fe0cda..a63508210 100644 --- a/packages/react-native-web/src/index.js +++ b/packages/react-native-web/src/index.js @@ -73,6 +73,9 @@ import TVEventHandler from './exports/TVEventHandler'; // plugins import DeviceEventEmitter from './exports/DeviceEventEmitter'; +// hooks +import useWindowDimensions from './exports/useWindowDimensions'; + export { // top-level API createElement as unstable_createElement, @@ -145,5 +148,7 @@ export { TimePickerAndroid, TVEventHandler, // plugins - DeviceEventEmitter + DeviceEventEmitter, + // hooks + useWindowDimensions };