Skip to content

Commit

Permalink
Allow injecting looser VirtualizedList
Browse files Browse the repository at this point in the history
Summary:
VirtualizedList_EXPERIMENTAL has a different flow signature from VirtualizedList. This does not cause an error in unit tests, which are untyped, but prevents injecting VirtualizedList_EXPERIMENTAL via the current API from flowtype. This diff updates the injection interface to allow a more relaxed type, validating safety at runtime.

Changelog:
[Internal][Changed] - Allow injecting looser VirtualizedList

Reviewed By: javache

Differential Revision: D38143682

fbshipit-source-id: 054bbc5092823f4e4413d69d3d72a7ecdd71a6a2
NickGerleman authored and facebook-github-bot committed Jul 27, 2022
1 parent ccbfdd7 commit f8e1fed
Showing 4 changed files with 69 additions and 12 deletions.
8 changes: 5 additions & 3 deletions Libraries/Lists/VirtualizedList.js
Original file line number Diff line number Diff line change
@@ -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<typeof ScrollView>,
...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);
66 changes: 61 additions & 5 deletions Libraries/Lists/VirtualizedListInjection.js
Original file line number Diff line number Diff line change
@@ -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<VirtualizedListProps> &
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<VirtualizedList>,
): Class<VirtualizedList> {
retrieved = true;
return injection
? verifyVirtualizedList(injection, defaultImplementation)
: defaultImplementation;
}

function verifyVirtualizedList(
injectedImplementation: ListImplementation,
defaultImplementation: Class<VirtualizedList>,
): Class<VirtualizedList> {
// 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] === '_');
}
2 changes: 1 addition & 1 deletion Libraries/Lists/VirtualizedList_EXPERIMENTAL.js
Original file line number Diff line number Diff line change
@@ -307,7 +307,7 @@ type OptionalProps = {|
legacyImplementation?: empty,
|};

type Props = {|
export type Props = {|
...React.ElementConfig<typeof ScrollView>,
...RequiredProps,
...OptionalProps,
5 changes: 2 additions & 3 deletions Libraries/Lists/__tests__/VirtualizedList-test.js
Original file line number Diff line number Diff line change
@@ -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');

0 comments on commit f8e1fed

Please sign in to comment.