skip to content
Andrei Calazans

React Native FlatList with Hooks

/ 3 min read

We want to render a list of finite items, it will render a list of random Startup names and also keep a local state of the selected favourites. 

FlatList Gif

Startup names list with React.Component, we could write it as the following:

const data = randomWords({ exactly: 30, wordsPerString: 2 });

export class StartUpNames extends React.Component {
  state = {
    favorites: [],
  };

  handlePress = (item) => {
    this.setState(prevState => {
      const { favorites } = prevState;
      const isFavorite = favorites.includes(item);
      return {
        favorites: isFavorite
          ? favorites.filter(title => title !== item)
          : [item, ...favorites],
      };
    });
  }

  renderItem = ({ item, index }) => {
    return (
      <Row
        title={item}
        isFavorite={this.state.favorites.includes(item)}
        onPress={this.handlePress}
      />
    );
  };

  // Names are randomly generated, add index for safety.
  keyExtractor = (item, index) => item + index;

  render() {
    const { navigation } = this.props;
    const { favorites } = this.state;
    return (
      <View style={styles.flex}>
        <Header
          navigate={navigation.navigate}
          onMore={() => navigation.navigate('FavoriteStartUpNames')}
          goBack={navigation.goBack}
        />
        <FlatList
          extraData={favorites}
          contentContainerStyle={styles.container}
          data={data} // data is a constant values in the File scope.
          renderItem={this.renderItem}
          keyExtractor={this.keyExtractor}
        />
      </View>
    );
  }
}

Special note to extraData={favorites} this is required since each item needs to know if it has been favourited. Besides the above, the FlatList is ordinary.

How can we re-write this with hooks?

const Row = ({ title, isFavorite, onPress }) => {
  return (
    <React.Fragment>
      <View style={styles.row}>
        <Text style={styles.rowText}>{title}</Text>
        <IconButton
          color={isFavorite ? colors.heartRed : colors.darkGray}
          icon="favorite"
          size={18}
          onPress={() => onPress(title)}
        />
      </View>
      <Divider />
    </React.Fragment>
  );
};

function keyExtractor(item: string, index: number) {
  return item + index;
}

const data = randomWords({ exactly: 30, wordsPerString: 2 });

function keyExtractor(item, index) {
  return item + index;
}

const renderItem = ({ item, index, favorites, setFavorite }) => {
  return <Row
    title={item}
    isFavorite={favorites.includes(item)}
    onPress={item => {
      setFavorite((favoriteItems) => {
        let isFavorite = favoriteItems.includes(item);
        if (isFavorite) {
          return favoriteItems.filter((title) => title !== item);
        }
        return [item, ...favoriteItems];
      });
    }}
  />;
};

export const StartUpNames = ({ navigation }) => {
  const [favorites, setFavorite] = useState([]);
  const renderItemCall = useCallback(({ item, index }) => renderItem({ item, index, favorites, setFavorite }));
  return (
    <View style={styles.flex}>
      <Header
        navigate={navigation.navigate}
        onMore={() => navigation.navigate('FavoriteStartUpNames')}
        goBack={navigation.goBack}
      />
      <FlatList
        extraData={favorites}
        contentContainerStyle={styles.container}
        data={data} // data is a constant values in the File scope.
        renderItem={renderItemCall}
        keyExtractor={keyExtractor}
      />
    </View>
  );
};

Straight forward. There is a mindset change here, to avoid inline functions on the props, we are using useCallbackFor example this line:

onMore={() => navigation.navigate('FavoriteStartUpNames')}

Could be turned into a useCallback.

const onMoreCall = useCallback(() => navigation.navigate('FavoriteStartUpNames'), []);
...
<FlatList
  ...
  onMore={onMoreCall}
/>

The [] means it has no dependencies to worry about. There is performance issue with having an extra data dependency in the list which is not related to hooks, everytime our favoritesupdates it re-renders the whole list. This is not desired.  To avoid unnecessary re-renders on the Row component, we can leverage the memo API provided by React. Here is how it would look like if we wrapped the Row component with it:

function propsAreEqual(prev, next) {
  return prev.isFavorite === next.isFavorite;
}

const Row = memo(({ title, isFavorite, onPress }) => {
  return (
    <React.Fragment>
      <View style={styles.row}>
        <Text style={styles.rowText}>{title}</Text>
        <IconButton
          color={isFavorite ? colors.heartRed : colors.darkGray}
          icon="favorite"
          size={18}
          onPress={() => onPress(title)}
        />
      </View>
      <Divider />
    </React.Fragment>
  );
}, propsAreEqual);

Previous to Hooks, we would need to transform Row into a class component and use shouldComponentUpdateto determine if the re-render was needed.

To conclude, hooks brought a new way to do the same old things.