Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 541
Ben Avatar asked Jul 24 '20 19:07

Ben


People also ask

What is difference between SectionList vs FlatList?

SectionList s are like FlatList s, but they can have section headers to separate groups of rows. SectionList s render each item in their input sections using the renderSectionHeader and renderItem prop. Each item in sections should be an object with a unique id (the key), and an array data of row data.

Can we use FlatList in react JS?

FlatList is implemented from the VirtualizedList component that takes care of displaying a limited number of items that will fit in the current view port of your mobile screen. The rest of the data is rendered as the user scrolls. The basic props like data and renderItem can be used to create a FlatList.

How do I use extraData in FlatList?

By passing extraData={selectedId} to FlatList we make sure FlatList itself will re-render when the state changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is a PureComponent and the prop comparison will not show any changes.


2 Answers

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.

like image 74
Sorio Avatar answered Nov 16 '22 01:11

Sorio


Suppose that the item to render is a Movie:

    _renderItem(info: ListRenderItemInfo<Movie>): ReactElement<Movie> {
        return <MovieTile movie={info.item} />;
    }

    render() {
        let movies = this.props.movies;

        return <FlatList
            data={movies}
            horizontal={true}
            renderItem={this._renderItem}
            keyExtractor={(item, i) => item.id.toString()}
            />;
    }
like image 39
Pnemonic Avatar answered Nov 16 '22 01:11

Pnemonic