Highlight a selected item in React-Native FlatList

Krishnan Sriram picture Krishnan Sriram · Oct 11, 2017 · Viewed 31.7k times · Source

I put together a simple React-native application to gets data from a remote service, loads it in a FlatList. When a user taps on an item, it should be highlighted and selection should be retained. I am sure such a trivial operation should not be difficult. I am not sure what I am missing.

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  FlatList,
  ActivityIndicator,
  Image,
  TouchableOpacity,
} from 'react-native';

export default class BasicFlatList extends Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      data: [],
      page: 1,
      seed: 1,
      error: null,
      refreshing: false,
      selectedItem:'null',
    };
  }

  componentDidMount() {
    this.makeRemoteRequest();
  }

  makeRemoteRequest = () => {
    const {page, seed} = this.state;
    const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
    this.setState({loading: true});
    fetch(url)
      .then(res => res.json())
      .then(res => {
        this.setState({
          data: page === 1 ? res.results : [...this.state.data, ...res.results],
          error: res.error || null,
          loading: false,
          refreshing: false
        });
      })
      .catch(error => {
        this.setState({error, loading: false});
      });
  };

  onPressAction = (rowItem) => {
    console.log('ListItem was selected');
    console.dir(rowItem);
    this.setState({
      selectedItem: rowItem.id.value
    });
  }

  renderRow = (item) => {
    const isSelectedUser = this.state.selectedItem === item.id.value;
    console.log(`Rendered item - ${item.id.value} for ${isSelectedUser}`);
    const viewStyle = isSelectedUser ? styles.selectedButton : styles.normalButton;
    return(
        <TouchableOpacity style={viewStyle} onPress={() => this.onPressAction(item)} underlayColor='#dddddd'>
          <View style={styles.listItemContainer}>
            <View>
              <Image source={{ uri: item.picture.large}} style={styles.photo} />
            </View>
            <View style={{flexDirection: 'column'}}>
              <View style={{flexDirection: 'row', alignItems: 'flex-start',}}>
                {isSelectedUser ?
                  <Text style={styles.selectedText}>{item.name.first} {item.name.last}</Text>
                  : <Text style={styles.text}>{item.name.first} {item.name.last}</Text>
                }
              </View>
              <View style={{flexDirection: 'row', alignItems: 'flex-start',}}>
                <Text style={styles.text}>{item.email}</Text>
              </View>
            </View>
          </View>
        </TouchableOpacity>
    );
  }

  render() {
    return(
      <FlatList style={styles.container}
        data={this.state.data}
        renderItem={({ item }) => (
          this.renderRow(item)
        )}
      />
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 50,
  },
  selectedButton: {
    backgroundColor: 'lightgray',
  },
  normalButton: {
    backgroundColor: 'white',
  },
  listItemContainer: {
    flex: 1,
    padding: 12,
    flexDirection: 'row',
    alignItems: 'flex-start',
  },
  text: {
    marginLeft: 12,
    fontSize: 16,
  },
  selectedText: {
    marginLeft: 12,
    fontSize: 20,
  },
  photo: {
    height: 40,
    width: 40,
    borderRadius: 20,
  },
});

When user taps on an item in the list, "onPress" method is invoked with the information on selected item. But the next step of highlight item in Flatlist does not happen. 'UnderlayColor' is of no help either.

Any help/advice will be much appreciated.

Answer

Maicon Gilton picture Maicon Gilton · Nov 21, 2018

You can do something like:

  1. For the renderItem, use something like a TouchableOpacity with an onPress event passing the index or id of the renderedItem;

  2. Function to add the selected item to a state:

    handleSelection = (id) => {
       var selectedId = this.state.selectedId
    
       if(selectedId === id)
         this.setState({selectedItem: null})
       else 
         this.setState({selectedItem: id})
    }
    
    handleSelectionMultiple = (id) => {
       var selectedIds = [...this.state.selectedIds] // clone state
    
       if(selectedIds.includes(id))
         selectedIds = selectedIds.filter(_id => _id !== id)
       else 
         selectedIds.push(id)
    
       this.setState({selectedIds})
    }
    
  3. FlatList:

    <FlatList
      data={data}
      extraData={
        this.state.selectedId     // for single item
        this.state.selectedIds    // for multiple items
      }
      renderItem={(item) => 
         <TouchableOpacity 
    
           // for single item
           onPress={() => this.handleSelection(item.id)}
           style={item.id === this.state.selectedId ? styles.selected : null} 
    
           // for multiple items
           onPress={() => this.handleSelectionMultiple(item.id)}
           style={this.state.selectedIds.includes(item.id) ? styles.selected : null} 
         >
            <Text>{item.name}</Text>
         </TouchableOpacity>
      }
    

    />

  4. Make a style for the selected item and that's it!