diff --git a/packages/react-native-web/src/exports/ImageBackground/__tests__/index.js b/packages/react-native-web/src/exports/ImageBackground/__tests__/index.js index 30a61b4a..eb1e925b 100644 --- a/packages/react-native-web/src/exports/ImageBackground/__tests__/index.js +++ b/packages/react-native-web/src/exports/ImageBackground/__tests__/index.js @@ -1,36 +1,39 @@ /* eslint-env jasmine, jest */ -import Image from '../../Image'; import ImageBackground from '..'; import React from 'react'; -import { shallow } from 'enzyme'; +import { render } from '@testing-library/react'; import Text from '../../Text'; +function findImage(container) { + return container.firstChild.firstChild; +} + describe('components/ImageBackground', () => { describe('prop "children"', () => { - it('render child content', () => { - const component = shallow( + test('render child content', () => { + const { getByText } = render( Hello World! ); - expect(component.find(Text)).toBeDefined(); + expect(getByText('Hello World!')).toBeDefined(); }); }); describe('prop "imageStyle"', () => { - it('sets the style of the underlying Image', () => { + test('sets the style of the underlying Image', () => { const imageStyle = { width: 40, height: 60 }; - const component = shallow(); - expect(component.find(Image).prop('style')).toContain(imageStyle); + const { container } = render(); + expect(findImage(container).getAttribute('style')).toBe('height: 60px; width: 40px;'); }); }); describe('prop "style"', () => { - it('sets the style of the container View', () => { + test('sets the style of the container View', () => { const style = { margin: 40 }; - const component = shallow(); - expect(component.prop('style')).toEqual(style); + const { container } = render(); + expect(container.firstChild.getAttribute('style')).toEqual('margin: 40px 40px 40px 40px;'); }); }); }); diff --git a/packages/react-native-web/src/exports/ImageBackground/index.js b/packages/react-native-web/src/exports/ImageBackground/index.js index 22022bbc..9b4f8e1d 100644 --- a/packages/react-native-web/src/exports/ImageBackground/index.js +++ b/packages/react-native-web/src/exports/ImageBackground/index.js @@ -10,11 +10,11 @@ import type { ImageProps } from '../Image'; import type { ViewProps } from '../View'; -import ensureComponentIsNative from '../../modules/ensureComponentIsNative'; +import setAndForwardRef from '../../modules/setAndForwardRef'; import Image from '../Image'; import StyleSheet from '../StyleSheet'; import View from '../View'; -import React from 'react'; +import React, { forwardRef, useImperativeHandle, useRef } from 'react'; type ImageBackgroundProps = { ...ImageProps, @@ -28,52 +28,58 @@ const emptyObject = {}; /** * Very simple drop-in replacement for which supports nesting views. */ -class ImageBackground extends React.Component { - setNativeProps(props: Object) { - // Work-around flow - const viewRef = this._viewRef; - if (viewRef) { - ensureComponentIsNative(viewRef); - viewRef.setNativeProps(props); +const ImageBackground = forwardRef((props, ref) => { + const { children, style = emptyObject, imageStyle, imageRef, ...rest } = props; + const { height, width } = StyleSheet.flatten(style); + + const containerRef = useRef(null); + + useImperativeHandle( + ref, + () => ({ + setNativeProps(props) { + if (containerRef.current != null) { + containerRef.current.setNativeProps(props); + } + } + }), + [containerRef] + ); + + const setRef = setAndForwardRef({ + getForwardedRef: () => ref, + setLocalRef: c => { + containerRef.current = c; } - } + }); - _viewRef: ?View = null; + return ( + + overwrites width and height styles + // (which is not quite correct), and these styles conflict with explicitly set styles + // of and with our internal layout model here. + // So, we have to proxy/reapply these styles explicitly for actual component. + // This workaround should be removed after implementing proper support of + // intrinsic content size of the . + width, + height, + zIndex: -1 + }, + imageStyle + ]} + /> + {children} + + ); +}); - _captureRef = (ref: View) => { - this._viewRef = ref; - }; - - render() { - const { children, style = emptyObject, imageStyle, imageRef, ...props } = this.props; - const { height, width } = StyleSheet.flatten(style); - - return ( - - overwrites width and height styles - // (which is not quite correct), and these styles conflict with explicitly set styles - // of and with our internal layout model here. - // So, we have to proxy/reapply these styles explicitly for actual component. - // This workaround should be removed after implementing proper support of - // intrinsic content size of the . - width, - height, - zIndex: -1 - }, - imageStyle - ]} - /> - {children} - - ); - } -} +ImageBackground.displayName = 'ImageBackground'; export default ImageBackground; diff --git a/packages/react-native-web/src/modules/setAndForwardRef/index.js b/packages/react-native-web/src/modules/setAndForwardRef/index.js new file mode 100644 index 00000000..9f006482 --- /dev/null +++ b/packages/react-native-web/src/modules/setAndForwardRef/index.js @@ -0,0 +1,58 @@ +/** + * 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. + * + * @flow + */ + +import * as React from 'react'; + +type Args = $ReadOnly<{| + getForwardedRef: () => ?React.Ref, + setLocalRef: (ref: React.ElementRef) => mixed +|}>; + +/** + * This is a helper function for when a component needs to be able to forward a ref + * to a child component, but still needs to have access to that component as part of + * its implementation. + * + * Its main use case is in wrappers for native components. + * + * Usage: + * + * function MyView(props) { + * const ref = useRef(null); + * + * function setRef = setAndForwardRef({ + * getForwardedRef: () => props.forwardedRef, + * setLocalRef: localRef => { + * ref.current = localRef; + * }, + * }); + * + * return ; + * } + * + * const MyViewWithRef = React.forwardRef((props, ref) => ( + * + * )); + */ + +export default function setAndForwardRef({ getForwardedRef, setLocalRef }: Args) { + return function forwardRef(ref: React.ElementRef) { + const forwardedRef = getForwardedRef(); + setLocalRef(ref); + + // Forward to user ref prop (if one has been specified) + if (typeof forwardedRef === 'function') { + // Handle function-based refs. String-based refs are handled as functions. + forwardedRef(ref); + } else if (typeof forwardedRef === 'object' && forwardedRef != null) { + // Handle createRef-based refs + forwardedRef.current = ref; + } + }; +}