diff --git a/Libraries/Lists/VirtualizedList.js b/Libraries/Lists/VirtualizedList.js index 6387e767be8..8604608e776 100644 --- a/Libraries/Lists/VirtualizedList.js +++ b/Libraries/Lists/VirtualizedList.js @@ -28,6 +28,7 @@ import { computeWindowedRenderLimits, keyExtractor as defaultKeyExtractor, } from './VirtualizeUtils'; +import * as VirtualizedListInjection from './VirtualizedListInjection'; import * as React from 'react'; const RefreshControl = require('../Components/RefreshControl/RefreshControl'); @@ -302,7 +303,7 @@ type OptionalProps = {| legacyImplementation?: empty, |}; -type Props = {| +export type Props = {| ...React.ElementConfig, ...RequiredProps, ...OptionalProps, @@ -2217,5 +2218,6 @@ const styles = StyleSheet.create({ }, }); -module.exports = (require('./VirtualizedListInjection').default - .unstable_VirtualizedList ?? VirtualizedList: typeof VirtualizedList); +module.exports = (VirtualizedListInjection.getOrDefault( + VirtualizedList, +): typeof VirtualizedList); diff --git a/Libraries/Lists/VirtualizedListInjection.js b/Libraries/Lists/VirtualizedListInjection.js index cc872a7ecfb..73cdbbeb61c 100644 --- a/Libraries/Lists/VirtualizedListInjection.js +++ b/Libraries/Lists/VirtualizedListInjection.js @@ -4,13 +4,69 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @flow + * @flow strict-local * @format */ 'use strict'; -import typeof VirtualizedList from './VirtualizedList'; -export default { - unstable_VirtualizedList: (null: ?VirtualizedList), -}; +import * as React from 'react'; +import type VirtualizedList from './VirtualizedList'; +import type {Props as VirtualizedListProps} from './VirtualizedList'; +import invariant from 'invariant'; + +export type ListImplementation = React.ComponentType & + interface {}; + +/** + * Global override to the VirtualizedList implementation used when imported + */ +let injection: ?ListImplementation; +let retrieved = false; + +export function inject(listImplementation: ListImplementation): void { + invariant( + !retrieved, + 'VirtualizedListInjection.inject() called after the injection was already retrieved', + ); + injection = listImplementation; +} + +export function getOrDefault( + defaultImplementation: Class, +): Class { + retrieved = true; + return injection + ? verifyVirtualizedList(injection, defaultImplementation) + : defaultImplementation; +} + +function verifyVirtualizedList( + injectedImplementation: ListImplementation, + defaultImplementation: Class, +): Class { + // The original VirtualizedList marks method as "private by convention" by + // prefixing with underscore. These methods may still be called at runtime + // by tests or other internals, so they cannot be truly private, and will be + // included in the Flow type of VirtualizedList. + // Allow the injection to have different private methods by allowing a loose + // Flow type, but check at runtime that the set of public properties matches. + if (__DEV__) { + for (const field of Object.keys(defaultImplementation)) { + if (isPublicField(field)) { + invariant( + injectedImplementation.hasOwnProperty(field), + `VirtualizedList injection missing field: "${field}"`, + ); + } + } + } + + // $FlowExpectedError + return injectedImplementation; +} + +function isPublicField(fieldName: string): boolean { + // Respect JSTransform public methods by double underscore (D33982339) + return fieldName.length > 0 && (fieldName[0] !== '_' || fieldName[1] === '_'); +} diff --git a/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js b/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js index d69b3d01a2a..64d866859e6 100644 --- a/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js +++ b/Libraries/Lists/VirtualizedList_EXPERIMENTAL.js @@ -307,7 +307,7 @@ type OptionalProps = {| legacyImplementation?: empty, |}; -type Props = {| +export type Props = {| ...React.ElementConfig, ...RequiredProps, ...OptionalProps, diff --git a/Libraries/Lists/__tests__/VirtualizedList-test.js b/Libraries/Lists/__tests__/VirtualizedList-test.js index 904d2f15513..d93c1f4f5f0 100644 --- a/Libraries/Lists/__tests__/VirtualizedList-test.js +++ b/Libraries/Lists/__tests__/VirtualizedList-test.js @@ -12,15 +12,14 @@ import React from 'react'; import ReactTestRenderer from 'react-test-renderer'; -import VirtualizedListInjection from '../VirtualizedListInjection'; +import * as VirtualizedListInjection from '../VirtualizedListInjection'; import VirtualizedList_EXPERIMENTAL from '../VirtualizedList_EXPERIMENTAL'; const useExperimentalList = process.env.USE_EXPERIMENTAL_VIRTUALIZEDLIST === 'true'; if (useExperimentalList) { - VirtualizedListInjection.unstable_VirtualizedList = - VirtualizedList_EXPERIMENTAL; + VirtualizedListInjection.inject(VirtualizedList_EXPERIMENTAL); } const VirtualizedList = require('../VirtualizedList');