J. Hesters
J. Hesters

Reputation: 14814

TypeScript React Native Flatlist: How to give renderItem the correct type of it's item?

I'm building a React Native app with TypeScript. renderItem complains that the destructured item implicitly has an any type. I googled and found this question and tried to implement what they teach here combined with the types in index.d.ts of the @types package for React Native.

export interface Props {
  emotions: Emotion[];
}

class EmotionsPicker extends PureComponent<Props> {
  keyExtractor = (item, index) => index;
  renderItem = ({ item }) => (
    <ListItem title={item.name} checkmark={item.checked} />
  );

  render() {
    return (
      <FlatList<Emotion>
        keyExtractor={this.keyExtractor}
        renderItem={this.renderItem}
        data={this.props.emotions}
      />
    );
  }
}

Unfortunately this does not work. How can I give item the type Emotion?

Upvotes: 66

Views: 72967

Answers (11)

aardvarkk
aardvarkk

Reputation: 15996

My problem was that I was importing FlatList from react-native-gesture-handler instead of react-native. The type inference didn't work properly in that scenario, but worked fine when I fixed the import.

The type of the item passed to renderItem should be inferrable from the type of the data array when properly done, and you shouldn't have to specify/override types.

Upvotes: 3

Muhammad Rafeh Atique
Muhammad Rafeh Atique

Reputation: 872

Founded amazing way to solve this problem


import { ListRenderItemInfo } from 'react-native'

interface PlayerDataStructure {
  imageUrl: string;
  doubleValue: string | number;
  singleValue: string | number;
  isFavorite: boolean;
  name: string;
  thirdRowFirstLabel: string;
  thirdRowSecondLabel: string;
  thirdRowThirdLabel: string;
  thirdRowFourthLabel: string;
  id: number
}

const player: Array<PlayerDataStructure> = [
  { imageUrl: imageUrl, doubleValue: '43', singleValue: '01', isFavorite: true, name: 'Jerome Bell', thirdRowFirstLabel: '19 y', thirdRowSecondLabel: '183 cm', thirdRowThirdLabel: '75 kg', thirdRowFourthLabel: 'Right (2HB)', id: 0 },
  { imageUrl: imageUrl, doubleValue: '43', singleValue: '01', isFavorite: false, name: 'Jerome Bell', thirdRowFirstLabel: '19 y', thirdRowSecondLabel: '183 cm', thirdRowThirdLabel: '75 kg', thirdRowFourthLabel: 'Right (2HB)', id: 1 },
];

  // Render Item
  const renderPlayerData = (object: ListRenderItemInfo<PlayerDataStructure>) => {
    const { item } = object;
    return (
      <PlayerCard imageUrl={item.imageUrl} doubleValue={item.doubleValue} singleValue={item.singleValue} isFavorite={item.isFavorite} name={item.name} thirdRowFirstLabel={item.thirdRowFirstLabel} thirdRowSecondLabel={item.thirdRowSecondLabel} thirdRowThirdLabel={item.thirdRowThirdLabel} thirdRowFourthLabel={item.thirdRowFourthLabel} />
    )
  }

 <FlatList
          numColumns={2}
          data={player}
          keyExtractor={item => item.id.toString()}
          renderItem={renderPlayerData}
          contentContainerStyle={{ gap: PLAYER_CARD_VERTICAL_PADDING, paddingTop: 67 }}
          columnWrapperStyle={{ justifyContent: 'space-between' }}
        />

Upvotes: 2

Hamza Regardless
Hamza Regardless

Reputation: 83

With TypeScript use this to avoid Type Error

keyExtractor={(item, index) => index.toString()}

In FlatList

  <FlatList
       data={list}
       keyExtractor={(item, index) => index.toString()} 
       ....
       />

The Type of index is Number and FlatList requires a string type value as its keys.

Upvotes: 2

Samuel Hulla
Samuel Hulla

Reputation: 7109

I feel like the answers here only address how to in better cases fix and in worse cases bypass the type checking, but fail to explain why it does not work or why it should be typed the way it is.

I'd like to provide some more clarity as to why this fails for most people as it's very poorly documented even on the official wiki.


Why you're getting error:

The component FlatList method does not expect a render function you are commonly used for passing (i.e.) standard react element constructor (props) => JSX.Element.

For better or for worse, FlatList's renderItem method is typed the following way:

type ListRenderItem<ItemT> = (info: ListRenderItemInfo<ItemT>) =>
   React.ReactElement | null;

This is because it does not expect a direct element constructor as you would standardly construct renderItem methods as you are used to in most React patterns.

Instead it expects a callback with a specific type. This is for internal handling of optimalization purposes, but it still is rather clunky when it comes to typing.

export interface ListRenderItemInfo<ItemT> {
    item: ItemT;
    index: number;
    separators: {
        highlight: () => void;
        unhighlight: () => void;
        updateProps: (select: 'leading' | 'trailing', newProps: any) => void;
    };
}

Proper way to type the function

So it means we should treat our own internal renderItem as a callback as well with specific item prop where we pass the specific <ItemT> generic

type Props = {
  emotions: Emotion[]
}

// This is the standard React function component
const Item = (({ title, checked }): Emotion) => (
   <ListItem title={title} checked={checked} />
)

// This is the callback for constructing the function component
const renderItem: ListRenderItem<Emotion> = ({ item }: ListRenderItemInfo<Emotion>) => (
   <Item title={item.title} checked={item.checked} />
)

As you can see, this might be fairly confusing on first glance and easy to mix-up. In the ListRenderItem<T> tells typescript, we're looking to create a function of type T (generic argument) which then constructs (returns) a Reat.ReactElement | null. But since React Components themselves are functions, we still need to pass the function to create one. This is where the confusion and the type error stems from.

This essentially

  1. Creates a callback of ListRenderItem<T>
  2. Which accepts only specific propType of ListRenderItemInfo<T> with the prop item
  3. Which then expects constructor function (props: T) => JSX.Element

instead of the the standard way of passing props and creating element from it. That's also why you're (were) getting the following errors:

Property 'property name' does not exist on ListRenderItem<any>

Upvotes: 5

devbiswasnegi
devbiswasnegi

Reputation: 316

const renderItem = ( {item}:{item:{ id:number, name:string , check:boolean }}) => { return ( ) }

Upvotes: 0

Champignon
Champignon

Reputation: 67

this solution works perfectly fine for me

interface Props {
    emotions: Emotion[];
}

class EmotionsPicker extends React.Component<Props> {
    render(): React.ReactNode {
        return (
            <FlatList<Emotion>
                data={this.props.emotions}
                keyExtractor={this.keyExtractor}
                renderItem={this.renderItem}
            />
        );
    }

    renderItem: ListRenderItem<Emotion> = (props: ListRenderItemInfo<Emotion>): ReactElement<Emotion, string | JSXElementConstructor<Emotion>> => {};

    keyExtractor = (item: Emotion): string => {};
}

Upvotes: -1

Leonardo Maldonado
Leonardo Maldonado

Reputation: 338

I found a solution that worked pretty well for me:

import { FlatList, ListRenderItemInfo } from 'react-native';


<FlatList
  data={data}
  renderItem={({ item }: ListRenderItemInfo<ItemType>) => (
    <ItemComponent item={item} />
  )}
  keyExtractor={(item: ItemType) => item.id}
/>

Upvotes: 21

Javan Poirier
Javan Poirier

Reputation: 71

If not an inline function, the correct type exported from React for this when destructured is ListRenderItemInfo<ItemT>:

export interface ListRenderItemInfo<ItemT> {
    item: ItemT;

    index: number;

    separators: {
        highlight: () => void;
        unhighlight: () => void;
        updateProps: (select: "leading" | "trailing", newProps: any) => void;
    };
}

Upvotes: 7

Yhozen
Yhozen

Reputation: 1317

I know it's an old question but people googling it might still appreciate it.

import { FlatList, ListRenderItem } from 'react-native'
/*
.
.
.
*/
  renderItem: ListRenderItem<Emoticon> = ({ item }) => (
    <ListItem title={item.name} checkmark={item.checked} />
  );

Upvotes: 112

Madara&#39;s Ghost
Madara&#39;s Ghost

Reputation: 175078

This is because at the time renderItem is defined, TypeScript has no way of knowing it's supposed to go as the renderItem prop of FlatList.

If you had skipped the variable and directly plopped the function in the prop, TypeScript should be able to infer it correctly:

export interface Props {
  emotions: Emotion[];
}

class EmotionsPicker extends PureComponent<Props> {    
  render() {
    return (
      <FlatList<Emotion>
        keyExtractor={(item, index) => index}
        renderItem={({ item }) => <ListItem title={item.name} checkmark={item.checked} />}
        data={this.props.emotions}
      />
    );
  }
}

Upvotes: 28

J. Hesters
J. Hesters

Reputation: 14814

I found a solution (though I don't know if it's ideal):

renderItem = ({ item }: { item: Emotion }) => (
  <ListItem title={item.name} checkmark={item.chosen} />
);

Upvotes: 72

Related Questions