Sankalp Singha Sankalp Singha -4 years ago 221
Javascript Question

How to filter data in ListView React-native?

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?

Answer Source

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download