Skip to content

Commit

Permalink
Fix FlatList item generic types (#5528)
Browse files Browse the repository at this point in the history
<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

I think I ran into a typescript types regression, where the
`Animated.FlatList` type was no longer generic. It always used the any
type and I couldn't provide a custom item type myself anymore. I found
it while upgrading from Reanimated v2 to v3. I think I managed to patch
the generated .d.ts file properly locally and this is what did the trick
for me.

If this isn't matching your code style or requirements, I'm happy to
work with you on making this work.

<!-- Explain the motivation for this PR. Include "Fixes #<number>" if
applicable. -->

## Test plan

<!-- Provide a minimal but complete code snippet that can be used to
test out this change along with instructions how to run it and a
description of the expected behavior. -->

Probably makes sense to see if it breaks anything in the example app but
other than that 🤷

---------

Co-authored-by: Tomasz Żelawski <[email protected]>
  • Loading branch information
jkadamczyk and tjzel authored Dec 29, 2023
1 parent 8a7c817 commit 9d365ae
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 37 deletions.
19 changes: 19 additions & 0 deletions __typetests__/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,25 @@ function AnimatedFlatListTest() {
/>
);
}

function AnimatedFlatListTestGenericParameterPresence() {
return (
<>
<Animated.FlatList<string> data={['a', 'b']} renderItem={() => null} />
{/* @ts-expect-error Properly detects wrong generic type */}
<Animated.FlatList<string> data={[1, 2]} renderItem={() => null} />
</>
);
}

function AnimatedFlatListTestGenericParameterDefaultsToAny() {
return (
<>
{/* @ts-expect-error Disable TypeScript item type inference */}
<Animated.FlatList renderItem={({ item }) => item.absurdProperty} />
</>
);
}
}

function MakeMutableTest() {
Expand Down
83 changes: 46 additions & 37 deletions src/reanimated2/component/FlatList.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
'use strict';
import type { ForwardedRef } from 'react';
import React, { forwardRef } from 'react';
import type {
FlatListProps,
Expand Down Expand Up @@ -60,48 +59,58 @@ interface AnimatedFlatListComplement<T> extends FlatList<T> {
getNode(): FlatList<T>;
}

export const ReanimatedFlatList = forwardRef(
(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
props: ReanimatedFlatListPropsWithLayout<any>,
ref: ForwardedRef<FlatList>
) => {
const { itemLayoutAnimation, skipEnteringExitingAnimations, ...restProps } =
props;
// We need explicit any here, because this is the exact same type that is used in React Native types.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const FlatListForwardRefRender = function <Item = any>(
props: ReanimatedFlatListPropsWithLayout<Item>,
ref: React.ForwardedRef<FlatList>
) {
const { itemLayoutAnimation, skipEnteringExitingAnimations, ...restProps } =
props;

// Set default scrollEventThrottle, because user expects
// to have continuous scroll events and
// react-native defaults it to 50 for FlatLists.
// We set it to 1 so we have peace until
// there are 960 fps screens.
if (!('scrollEventThrottle' in restProps)) {
restProps.scrollEventThrottle = 1;
}
// Set default scrollEventThrottle, because user expects
// to have continuous scroll events and
// react-native defaults it to 50 for FlatLists.
// We set it to 1, so we have peace until
// there are 960 fps screens.
if (!('scrollEventThrottle' in restProps)) {
restProps.scrollEventThrottle = 1;
}

const CellRendererComponent = React.useMemo(
() => createCellRendererComponent(itemLayoutAnimation),
[]
);
const CellRendererComponent = React.useMemo(
() => createCellRendererComponent(itemLayoutAnimation),
[]
);

const animatedFlatList = (
<AnimatedFlatList
ref={ref}
{...restProps}
CellRendererComponent={CellRendererComponent}
/>
);
const animatedFlatList = (
// @ts-expect-error In its current type state, createAnimatedComponent cannot create generic components.
<AnimatedFlatList
ref={ref}
{...restProps}
CellRendererComponent={CellRendererComponent}
/>
);

if (skipEnteringExitingAnimations === undefined) {
return animatedFlatList;
}

if (skipEnteringExitingAnimations === undefined) {
return animatedFlatList;
}
return (
<LayoutAnimationConfig skipEntering skipExiting>
{animatedFlatList}
</LayoutAnimationConfig>
);
};

return (
<LayoutAnimationConfig skipEntering skipExiting>
{animatedFlatList}
</LayoutAnimationConfig>
);
export const ReanimatedFlatList = forwardRef(FlatListForwardRefRender) as <
// We need explicit any here, because this is the exact same type that is used in React Native types.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ItemT = any
>(
props: ReanimatedFlatListPropsWithLayout<ItemT> & {
ref?: React.ForwardedRef<FlatList>;
}
);
) => React.ReactElement;

export type ReanimatedFlatList<T> = typeof AnimatedFlatList &
AnimatedFlatListComplement<T>;

0 comments on commit 9d365ae

Please sign in to comment.