How to filter data in ListView React-native?

15,792

It's a little hard to follow what's going on here. I would simplify it to be like so:

class AssetsList extends Component {
  state = {};

  componentDidMount() {
    return this.props.assetsFetch();
  }

  onSearchChange(text) {
    this.setState({
      searchTerm: text
    });
  }

  renderRow(asset) {
    return (
      <ListItem thumbnail>
          <Left>
              <HealthImage health={asset.health} />
          </Left>
          <Body>
              <Text>{asset.name}</Text>

              <NText style={styles.nText}>
                Location: {asset.location} |
                Serial: {asset.serial_number}
              </NText>
              <NText>
                Total Samples: {asset.total_samples}
              </NText>

          </Body>
          <Right>
              <Button transparent onPress={() => Actions.assetShow()}>
                  <Text>View</Text>
              </Button>
          </Right>
      </ListItem>
    );
  }

  getFilteredAssets() {

  }

  render() {
    const filteredAssets = this.state.searchTerm
      ? this.props.assets.filter(asset => {
          return asset.name.indexOf(this.state.searchTerm) > -1;
        })
      : this.props.assets;
    const dataSource = ds.cloneWithRows(filteredAssets);
    return (
     <Input
                  placeholder="Search"
                  onChangeText={this.onSearchChange.bind(this)}
                />
        <ListView
          enableEmptySections
          dataSource={dataSource}
          renderRow={this.renderRow}
        />
    );
  }
}

const mapStateToProps = state => {
  return {
    assets: _.values(state.assets.asset),
    spinner: state.assets.asset_spinner
  };
};

export default connect(mapStateToProps, { assetsFetch })(AssetsList);

A few points:

  1. Your component is stateful. There is one piece of state that belongs only to the component: the search term. Keep that in component state.
  2. Don't change the data source in life cycle functions. Do it the latest point you know it's needed: in render.
  3. I'm guessing that there's something async in assetFetch, so you probably should return it in componentDidMount.
  4. I changed from componentWillMount to componentDidMount. It's recommended to put async fetching componentDidMount. This can matter if you ever do server side rendering.
  5. Skip filtering if there is no search term. This would only matter if the list is very large.

One thing I have a little concern with is the pattern of fetching inside a component, putting it in global state, and then relying on that component to react to the global state change. Thus changing global state becomes a side effect of simply viewing something. I assume you are doing it because assets is used elsewhere, and this is a convenient point to freshen them from the server so that they will show up in other components that do not fetch them. This pattern can result in hard-to-find bugs.

Share:
15,792
Sankalp Singha
Author by

Sankalp Singha

I am a professional Musician(Guitarist)/Composer/Sound Engineer and a passionate Programmer. I love Rails and the FrontEnd Stack of JS/CSS and HTML. I simply love Web Development. I am also an avid Network Security and Web Application Security Analyst. My strong areas of programming include : Rails, VueJs, Nginx, Ansible, PHP, HTML5, CSS3, Jquery/JS I am always tinkering and rejuvenating myself with the new tech stuff on the block and always trying to make or break stuff. In my free time you can catch me recording in my home studio. :)

Updated on June 11, 2022

Comments

  • Sankalp Singha
    Sankalp Singha almost 2 years

    I am trying to filter my array object list and then trying to display in the ListView with new DataSource. However, the list is not getting filtered. I know that my filter function works correctly. ( I checked it in the console.log )

    I am using Redux to map my state to prop. And then trying to filter the prop. Is this the wrong way?

    Here is my code:

    /*global fetch:false*/
    import _ from 'lodash';
    import React, { Component } from 'react';
    import { ListView, Text as NText } from 'react-native';
    import { connect } from 'react-redux';
    import { Actions } from 'react-native-router-flux';
    import {
      Container, Header, Item,
      Icon, Input, ListItem, Text,
      Left, Right, Body, Button
    } from 'native-base';
    
    
    import Spinner from '../common/Spinner';
    import HealthImage from '../misc/HealthImage';
    import { assetsFetch } from '../../actions';
    
    const ds = new ListView.DataSource({
      rowHasChanged: (r1, r2) => r1 !== r2
    });
    
    class AssetsList extends Component {
      componentWillMount() {
        this.props.assetsFetch();
    
        // Implementing the datasource for the list View
        this.createDataSource(this.props.assets);
      }
    
      componentWillReceiveProps(nextProps) {
        // Next props is the next set of props that this component will be rendered with.
        // this.props is still equal to the old set of props.
        this.createDataSource(nextProps.assets);
      }
    
      onSearchChange(text) {
        const filteredAssets = this.props.assets.filter(
          (asset) => {
            return asset.name.indexOf(text) !== -1;
          }
        );
    
        this.dataSource = ds.cloneWithRows(_.values(filteredAssets));
      }
    
      createDataSource(assets) {
        this.dataSource = ds.cloneWithRows(assets);
      }
    
    
      renderRow(asset) {
        return (
          <ListItem thumbnail>
              <Left>
                  <HealthImage health={asset.health} />
              </Left>
              <Body>
                  <Text>{asset.name}</Text>
    
                  <NText style={styles.nText}>
                    Location: {asset.location} |
                    Serial: {asset.serial_number}
                  </NText>
                  <NText>
                    Total Samples: {asset.total_samples}
                  </NText>
    
              </Body>
              <Right>
                  <Button transparent onPress={() => Actions.assetShow()}>
                      <Text>View</Text>
                  </Button>
              </Right>
          </ListItem>
        );
      }
    
    
    
      render() {
        return (
         <Input
                      placeholder="Search"
                      onChangeText={this.onSearchChange.bind(this)}
                    />
            <ListView
              enableEmptySections
              dataSource={this.dataSource}
              renderRow={this.renderRow}
            />
    
    
        );
      }
    }
    
    const mapStateToProps = state => {
      return {
        assets: _.values(state.assets.asset),
        spinner: state.assets.asset_spinner
      };
    };
    
    export default connect(mapStateToProps, { assetsFetch })(AssetsList);
    

    What am I doing wrong here?