diff --git a/Libraries/Components/ActivityIndicator/ActivityIndicator.js b/Libraries/Components/ActivityIndicator/ActivityIndicator.js
index 7438d1e35..65809a323 100644
--- a/Libraries/Components/ActivityIndicator/ActivityIndicator.js
+++ b/Libraries/Components/ActivityIndicator/ActivityIndicator.js
@@ -10,19 +10,15 @@
'use strict';
-const ColorPropType = require('ColorPropType');
-const NativeMethodsMixin = require('NativeMethodsMixin');
const Platform = require('Platform');
const ProgressBarAndroid = require('ProgressBarAndroid');
-const PropTypes = require('prop-types');
const React = require('React');
const StyleSheet = require('StyleSheet');
const View = require('View');
-const ViewPropTypes = require('ViewPropTypes');
-const createReactClass = require('create-react-class');
const requireNativeComponent = require('requireNativeComponent');
+import type {NativeComponent} from 'ReactNative';
import type {ViewProps} from 'ViewPropTypes';
let RCTActivityIndicator;
@@ -31,113 +27,103 @@ const GRAY = '#999999';
type IndicatorSize = number | 'small' | 'large';
+type IOSProps = $ReadOnly<{|
+ /**
+ * Whether the indicator should hide when not animating (true by default).
+ *
+ * See http://facebook.github.io/react-native/docs/activityindicator.html#hideswhenstopped
+ */
+ hidesWhenStopped?: ?boolean,
+|}>;
type Props = $ReadOnly<{|
...ViewProps,
+ ...IOSProps,
+ /**
+ * Whether to show the indicator (true, the default) or hide it (false).
+ *
+ * See http://facebook.github.io/react-native/docs/activityindicator.html#animating
+ */
animating?: ?boolean,
+
+ /**
+ * The foreground color of the spinner (default is gray).
+ *
+ * See http://facebook.github.io/react-native/docs/activityindicator.html#color
+ */
color?: ?string,
- hidesWhenStopped?: ?boolean,
+
+ /**
+ * Size of the indicator (default is 'small').
+ * Passing a number to the size prop is only supported on Android.
+ *
+ * See http://facebook.github.io/react-native/docs/activityindicator.html#size
+ */
size?: ?IndicatorSize,
|}>;
-type DefaultProps = {
- animating: boolean,
- color: ?string,
- hidesWhenStopped: boolean,
- size: IndicatorSize,
-};
-
/**
* Displays a circular loading indicator.
*
* See http://facebook.github.io/react-native/docs/activityindicator.html
*/
-const ActivityIndicator = ((createReactClass({
- displayName: 'ActivityIndicator',
- mixins: [NativeMethodsMixin],
+const ActivityIndicator = (
+ props: $ReadOnly<{|
+ ...Props,
+ forwardedRef?: ?React.Ref<'RCTActivityIndicatorView'>,
+ |}>,
+) => {
+ const {onLayout, style, forwardedRef, ...restProps} = props;
+ let sizeStyle;
- propTypes: {
- ...ViewPropTypes,
- /**
- * Whether to show the indicator (true, the default) or hide it (false).
- *
- * See http://facebook.github.io/react-native/docs/activityindicator.html#animating
- */
- animating: PropTypes.bool,
- /**
- * The foreground color of the spinner (default is gray).
- *
- * See http://facebook.github.io/react-native/docs/activityindicator.html#color
- */
- color: ColorPropType,
- /**
- * Size of the indicator (default is 'small').
- * Passing a number to the size prop is only supported on Android.
- *
- * See http://facebook.github.io/react-native/docs/activityindicator.html#size
- */
- size: PropTypes.oneOfType([
- PropTypes.oneOf(['small', 'large']),
- PropTypes.number,
- ]),
- /**
- * Whether the indicator should hide when not animating (true by default).
- *
- * @platform ios
- *
- * See http://facebook.github.io/react-native/docs/activityindicator.html#hideswhenstopped
- */
- hidesWhenStopped: PropTypes.bool,
- },
+ switch (props.size) {
+ case 'small':
+ sizeStyle = styles.sizeSmall;
+ break;
+ case 'large':
+ sizeStyle = styles.sizeLarge;
+ break;
+ default:
+ sizeStyle = {height: props.size, width: props.size};
+ break;
+ }
- getDefaultProps(): DefaultProps {
- return {
- animating: true,
- color: Platform.OS === 'ios' ? GRAY : null,
- hidesWhenStopped: true,
- size: 'small',
- };
- },
+ const nativeProps = {
+ ...restProps,
+ ref: forwardedRef,
+ style: sizeStyle,
+ styleAttr: 'Normal',
+ indeterminate: true,
+ };
- render() {
- const {onLayout, style, ...props} = this.props;
- let sizeStyle;
+ return (
+
+ {Platform.OS === 'ios' ? (
+
+ ) : (
+
+ )}
+
+ );
+};
- switch (props.size) {
- case 'small':
- sizeStyle = styles.sizeSmall;
- break;
- case 'large':
- sizeStyle = styles.sizeLarge;
- break;
- default:
- sizeStyle = {height: props.size, width: props.size};
- break;
- }
+// $FlowFixMe - TODO T29156721 `React.forwardRef` is not defined in Flow, yet.
+const ActivityIndicatorWithRef = React.forwardRef((props: Props, ref) => {
+ return ;
+});
- const nativeProps = {
- ...props,
- style: sizeStyle,
- styleAttr: 'Normal',
- indeterminate: true,
- };
-
- return (
-
- {Platform.OS === 'ios' ? (
-
- ) : (
-
- )}
-
- );
- },
-}): any): React.ComponentType);
+ActivityIndicatorWithRef.defaultProps = {
+ animating: true,
+ color: Platform.OS === 'ios' ? GRAY : null,
+ hidesWhenStopped: true,
+ size: 'small',
+};
+ActivityIndicatorWithRef.displayName = 'ActivityIndicator';
if (Platform.OS === 'ios') {
RCTActivityIndicator = requireNativeComponent(
'RCTActivityIndicatorView',
- ActivityIndicator,
+ null,
{nativeOnly: {activityIndicatorViewStyle: true}},
);
}
@@ -157,4 +143,4 @@ const styles = StyleSheet.create({
},
});
-module.exports = ActivityIndicator;
+module.exports = (ActivityIndicatorWithRef: Class>);
diff --git a/jest/mockComponent.js b/jest/mockComponent.js
index e22c34cca..6d5a80b47 100644
--- a/jest/mockComponent.js
+++ b/jest/mockComponent.js
@@ -20,9 +20,24 @@ module.exports = (moduleName, instanceMethods) => {
render() {
const name = RealComponent.displayName || RealComponent.name;
+ const props = Object.assign({}, RealComponent.defaultProps);
+
+ if (this.props) {
+ Object.keys(this.props).forEach(prop => {
+ // We can't just assign props on top of defaultProps
+ // because React treats undefined as special and different from null.
+ // If a prop is specified but set to undefined it is ignored and the
+ // default prop is used instead. If it is set to null, then the
+ // null value overwrites the default value.
+ if (this.props[prop] !== undefined) {
+ props[prop] = this.props[prop];
+ }
+ });
+ }
+
return React.createElement(
name.replace(/^(RCT|RK)/, ''),
- this.props,
+ props,
this.props.children,
);
}