Выделите выбранный элемент в React-Native FlatList

Я собрал простое приложение с поддержкой React, которое получает данные из удаленной службы и загружает их в FlatList. Когда пользователь нажимает на элемент, он должен быть выделен, а выбор должен быть сохранен. Я уверен, что такая тривиальная операция не должна быть сложной. Я не уверен, что мне не хватает.

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,
  },
});

Когда пользователь нажимает на элемент в списке, вызывается метод onPress с информацией о выбранном элементе. Но следующего шага выделения пункта в Flatlist не происходит. «UnderlayColor» тоже не помогает.

Мы будем очень благодарны за любую помощь / совет.


person Krishnan Sriram    schedule 11.10.2017    source источник
comment
Вам также нужно будет изменить ваши данные (состояние), в вашей onPressAction func замените setState на this.setState({selectedItem: rowItem.id.value, data: {...this.state.data}});. Но это не рекомендуется.   -  person Pir Shukarullah Shah    schedule 11.10.2017
comment
Я пробовал это. Не помогло   -  person Krishnan Sriram    schedule 12.10.2017
comment
@KrishnanSriram, у меня такая же проблема. работает ли ответ, который вы выбрали ниже?   -  person jerinho.com    schedule 07.09.2018


Ответы (4)


Вместо this.state.selectedItem и установки с / проверки для rowItem.id.value я бы рекомендовал использовать объект Map с парами ключ: значение, как показано в примере документации RN FlatList: https://facebook.github.io/react-native/docs/flatlist.html. Взгляните также на документы js Map: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map.

Свойство extraData, рекомендованное @ j.I-V, обеспечит повторный рендеринг, когда this.state.selected изменяется при выборе.

Ваш onPressAction, очевидно, немного изменится по сравнению с приведенным ниже примером в зависимости от того, хотите ли вы ограничить количество вариантов выбора в любой момент времени или не разрешать пользователю переключать выбор и т. Д.

Кроме того, хотя в этом нет необходимости, я предпочитаю использовать другой класс или чистый компонент для компонента renderItem; в итоге выглядит примерно так:

export default class BasicFlatList extends Component {
  state = {
    otherStateStuff: ...,
    selected: (new Map(): Map<string, boolean>) //iterable object with string:boolean key:value pairs
  }

  onPressAction = (key: string) => {
    this.setState((state) => {
      //create new Map object, maintaining state immutability
      const selected = new Map(state.selected);
      //remove key if selected, add key if not selected
      this.state.selected.has(key) ? selected.delete(key, !selected.get(key)) : selected.set(key, !selected.get(key));
      return {selected};
    });
  }

  renderRow = (item) => {
    return (
        <RowItem
          {...otherProps}
          item={item}
          onPressItem={this.onPressAction}
          selected={!!this.state.selected.get(item.key)} />
    );
  }

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


class RowItem extends Component {
  render(){
    //render styles and components conditionally using this.props.selected ? _ : _
    
    return (
      <TouchableOpacity onPress={this.props.onPressItem}>
        ...
      </TouchableOpacity>
    )
  }
}

person swimisbell    schedule 29.11.2017
comment
Почему у delete 2 аргумента? - person Kailash Uniyal; 29.05.2021

Вы можете сделать что-то вроде:

  1. Для renderItem используйте что-то вроде TouchableOpacity с событием onPress, передающим индекс или идентификатор renderedItem;

  2. Функция добавления выбранного элемента в состояние:

    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. Сделайте стиль для выбранного предмета и все!

person Maicon Gilton    schedule 21.11.2018
comment
Как я могу сделать это для нескольких товаров? - person Ranjit; 01.07.2019
comment
@Ranjit Немного поздно, но я отредактировал свой ответ на оба случая - person Maicon Gilton; 20.06.2020
comment
как я могу ограничить выбор пользователя двумя элементами? - person gSaenz; 27.06.2020
comment
@gSaenz Вы можете сделать что-то вроде функции handleSelectionMultiple: if (selectedIds.length ›2) return - person Maicon Gilton; 27.06.2020
comment
Это превращает все элементы в моем плоском списке в выбранный стиль. Есть идеи, что это за исправление? - person kalculated; 17.05.2021
comment
@kalculated Вы проверяли значения this.state.selectedIds? - person Maicon Gilton; 17.05.2021
comment
@MaiconGilton, привет! Я понял, что сравниваю неправильный идентификатор с selectedId. Все заработало, проголосовали за! - person kalculated; 18.05.2021

Вы должны передать свойство extraData в свой FlatList, чтобы он повторно отображал ваши элементы на основе вашего выбора.

Здесь :

<FlatList style={styles.container}
    data={this.state.data}
    extraData={this.state.selectedItem}
    renderItem={({ item }) => (
      this.renderRow(item)
    )}
 />

Источник: https://facebook.github.io/react-native/docs/flatlist < / а>

Убедитесь, что все, от чего зависит ваша функция renderItem, передается как опора (например, extraData), которая не === после обновлений, иначе ваш пользовательский интерфейс может не обновляться при изменениях.

person j.l-V    schedule 02.11.2017

Первый

  constructor() {
    super();
    this.state = {
      selectedIds:[]
    };
  }

Второй

handleSelectionMultiple = async (id) => {
var selectedIds = [...this.state.selectedIds] // clone state
if(selectedIds.includes(id))
  selectedIds = selectedIds.filter(_id => _id !== id)
else 
  selectedIds.push(id)
await this.setState({selectedIds})
}

В третьих

<CheckBox
     checked={this.state.selectedIds.includes(item.expense_detail_id) ? true : false}
     onPress={()=>this.handleSelectionMultiple(item.expense_detail_id)}
 />

Наконец, я получил решение своей проблемы из ответа Майкон Гилтон.

person Shaik Ansar    schedule 17.10.2020