React FlatList with TypeScript

TypeScript is really great, just for the moment I feel like I work for TypeScript more than TypeScript works for me.

I have a FlatList that renders restaurant results in a Carousel.

 const renderRestaurantRows = ({ item }) => (
    <Carousel
      id={item.id}
      name={item.name}
    />
  );
  const renderBottomSheetRestaurantList = () => (
    <View style={[styles.listView, { height: topSnapPoint }]}>
      <FlatList
        data={restaurants}
        keyExtractor={(item) => `row-${item.id}`}
        renderItem={renderRestaurantRows}
      />
    </View>
  );

TypeScript complains about item with Binding element 'item' implicitly has an 'any' type – makes sense. So I try to tell it what to expect:

  interface RestaurantItem {
    item: {
      id: string;
      name: string;
    };
  }
  const renderRestaurantRows = ({ item }: RestaurantItem) => (
    <Carousel
      id={item.id}
      name={item.name}
    />
  );

But then TS complains about data={restaurants] with Type 'Restaurant[]' is not assignable to type 'readonly { id: string; name: string; imageUrl: string; rating: number; reviews: number; } and a ton of other information.

Any chance someone can share a solution AND explain how to find such a solution in the future for other similar cases?

UPDATE

restaurants is fetched from a custom hook. It is defined as an array of Restaurant objects:

interface Restaurant {
  id: string;
  name: string;
}
export default function useRestaurantSearch() {
  const [restaurants, setRestaurants] = useState<Restaurant[] | null>(null);
  ...

UPDATE 2

Guys thanks for the comments, following the two suggestions, I rewrote the code:

import useRestaurantSearch from '../hooks/useRestaurantSearch';
export default function RestaurantPage() {
  const renderRestaurantRows = ({ item }: Restaurant) => (
    <Carousel
      id={item.id}
      name={item.name}
    />
  );
  const renderBottomSheetRestaurantList = () => (
    <View style={[styles.listView, { height: topSnapPoint }]}>
      <FlatList
        data={restaurants}
        keyExtractor={(item) => `row-${item.id}`}
        renderItem={renderRestaurantRows}
      />
    </View>
  );

This time I am getting an error for { item }: Restaurant with Cannot find name 'Restaurant'. And it’s not surprising because Restaurant is defined in an external hooks file ../hooks/useRestaurantSearch. Do I need to import it somehow?

UPDATE 3

After hours of playing around with this, I got to this point:

  const renderRestaurantRows = (result: { item: Restaurant }) => {
    return <Carousel {...result.item} />;
  };
  const renderBottomSheetRestaurantList = () => (
    <View style={[styles.listView, { height: topSnapPoint }]}>
      <FlatList
        data={restaurants}
        keyExtractor={(item) => `row-${item.id}`}
        renderItem={renderRestaurantRows}
      />
    </View>
  );

renderItem generates an object which contains each restaurant data under an item key. So for example, one of the objects could be:

{
  "index": 18,
  "item": Object {
    "coordinates": Object {
      "latitude": 123,
      "longitude": -123,
    },
    "id": "dfg987fshjsdfh",
    "name": "Yummy Food",
  },
  "separators": Object {
    "highlight": [Function highlight],
    "unhighlight": [Function unhighlight],
    "updateProps": [Function updateProps],
  },
}

When I pass a result object, I can let TS know about item inside the object and casting it to the Restaurant type. I do it with (result: { item: Restaurant }). However, when I try to directly destructure the resultobject with({ item }: Restaurant)it gives me the errorProperty ‘item’ does not exist on type ‘Restaurant’`. Any idea why?

Answer

Try importing ListRenderItem and type the const receiving the function:

import { ListRenderItem } from 'react-native';
...
const renderRestaurantRows: ListRenderItem<Restaurant> = ({ item }) => (
  <Carousel
    id={item.id}
    name={item.name}
  />
);

What you’re really getting as arguments from the renderItems is:

ListRenderItem

Where ItemT is your type, Restaurant.

Leave a Reply

Your email address will not be published. Required fields are marked *