Recyclerlistview: TypeError: undefined is not an object (evaluating 'this._layouts[i].x')

Created on 18 Aug 2018  路  8Comments  路  Source: Flipkart/recyclerlistview

I'm trying to implement search (as the user types in the search box) on the list. However, I'm getting the following error TypeError: undefined is not an object (evaluating 'this._layouts[i].x').

When the search string (value) is empty and the list is updated with all the elements, the error occurs. I also tried different conditions such as returning originalList when value is '', but it throws the same error as the list is updated with all the elements.

This is my code-

constructor(props) {
    super(props);
    let list = [{ name: 'Item1' }, { name: 'Item2' }];

    this._dataProvider = new DataProvider((r1, r2) => {
        return r1.name !== r2.name;
    });

    this.state = {
        list,
        originalList: _.clone(list),
        dataProvider: this._dataProvider.cloneWithRows(list)
    };
}

search(value) {
    this.setState(prevState => {
        return {
            list: _.filter(prevState.originalList, item=> {
                return value == '' || _.includes(item.name, value);
            })
        };
    });
}

render() {
    return (
        <Input
            placeholder="Search"
            autoFocus
            onChangeText={value => {
                this.search(value);
            }}
        />
        <RecyclerListView
            layoutProvider={this._layoutProvider}
            dataProvider={this.state.dataProvider.cloneWithRows(this.state.list)}
            rowRenderer={this._rowRenderer.bind(this)}
        />
    );
}

Any help would be much appreciated.

Most helpful comment

Hey @sksin28
Thanks for pointing out this issue but even after making the change you recommended, still the issue exists. I got a workaround(hack) for this but still didn't find a good solution.

All 8 comments

Try out the beta version and see if you face the same issue npm install --save recyclerlistview@beta

I tried the beta version but to no avail. I'm still getting the same error. :(

Debugging through the code, I found out the function where error occur, though I've not investigated how it can be fixed.

In the layoutManager.js, we have the WrapGridLayoutManager.prototype._locateFirstNeighbourIndex function, where the error is thrown.

    WrapGridLayoutManager.prototype._locateFirstNeighbourIndex = function (startIndex) {
        if (startIndex === 0) {
            return 0;
        }
        var i = startIndex - 1;
        for (; i >= 0; i--) {
            if (this._isHorizontal) {
                if (this._layouts[i].y === 0) {
                    break;
                }
            }
            else if (this._layouts[i].x === 0) {
                break;
            }
        }
        return i;
    };

Considering the case where the original list has 1000 items, the search function modifies the list variable in state and the _locateFirstNeighbourIndex function gets the startIndex value as 1000 and the size of this._layouts is 1000. After filtering (due to search function), let's consider the list size is 800.

When the search string is '', the startIndex is now 1000 but the size of this._layouts is 800. So, either the startIndex or the this._layouts is not updated correctly.

I am not sure how it can be fixed.

@naqvitalha @sksin28 I am facing the same issue , did you find any solution for it?

Can you share a repro on expo or, git so that I can quickly check this?

@naqvitalha
You can find the repro here- https://github.com/sksin28/recyclerlist-repro
Or in expo- https://snack.expo.io/rk_j9yYtQ

The issue is when you type '1' in search box, 'Item 1' appears in list. However, when you clear search box, instead of returning both 'Item 1' and 'Item 2', it throws an error.

It would be great to find the root cause of the issue. Thanks.

The issue has been resolved. The dataprovider was not passed correctly which I believe resulted in dataprovider and list not being in sync.

So that no one else faces this issue, this is what my earlier code looked like-

import * as React from "react";
import { Text, View, TextInput } from "react-native";

import _ from "lodash";

import {
  DataProvider,
  LayoutProvider,
  RecyclerListView
} from "recyclerlistview";

export default class App extends React.Component {
  constructor(props) {
    super(props);

    let list = [{ name: "Item 1" }, { name: "Item 2" }];

    this._dataProvider = new DataProvider((r1, r2) => {
      return r1.name !== r2.name;
    });

    this._layoutProvider = new LayoutProvider(
      index => {
        return 1;
      },
      (type, dim) => {
        dim.width = 400;
        dim.height = 120;
      }
    );

    this.state = {
      list,
      originalList: _.clone(list),
      dataProvider: this._dataProvider.cloneWithRows(list)
    };
  }

  search(value) {
    this.setState(prevState => {
      return {
        list: _.filter(prevState.originalList, item => {
          return value == "" || _.includes(item.name, value);
        })
      };
    });
  }

  _rowRenderer(type, item) {
    return (
      <View>
        <Text>{item.name}</Text>
      </View>
    );
  }

  render() {
    return (
      <View style={{ marginTop: 50, width: 400, height: 400 }}>
        <TextInput
          style={{ width: "100%" }}
          placeholder="Search"
          placeholderTextColor="red"
          onChangeText={value => {
            this.search(value);
          }}
        />
        <RecyclerListView
          layoutProvider={this._layoutProvider}
          dataProvider={this.state.dataProvider.cloneWithRows(this.state.list)}
          rowRenderer={this._rowRenderer.bind(this)}
        />
      </View>
    );
  }
}

After changing the dataprovider property, the code looks like-

import * as React from "react";
import { Text, View, TextInput } from "react-native";

import _ from "lodash";

import {
  DataProvider,
  LayoutProvider,
  RecyclerListView
} from "recyclerlistview";

export default class App extends React.Component {
  constructor(props) {
    super(props);

    let list = [{ name: "Item 1" }, { name: "Item 2" }];

    this._dataProvider = new DataProvider((r1, r2) => {
      return r1.name !== r2.name;
    });

    this._layoutProvider = new LayoutProvider(
      index => {
        return 1;
      },
      (type, dim) => {
        dim.width = 400;
        dim.height = 120;
      }
    );

    this.state = {
      list,
      originalList: _.clone(list)
    };
  }

  search(value) {
    this.setState(prevState => {
      return {
        list: _.filter(prevState.originalList, item => {
          return value == "" || _.includes(item.name, value);
        })
      };
    });
  }

  _rowRenderer(type, item) {
    return (
      <View>
        <Text>{item.name}</Text>
      </View>
    );
  }

  render() {
    return (
      <View style={{ marginTop: 50, width: 400, height: 400 }}>
        <TextInput
          style={{ width: "100%" }}
          placeholder="Search"
          placeholderTextColor="red"
          onChangeText={value => {
            this.search(value);
          }}
        />
        <RecyclerListView
          layoutProvider={this._layoutProvider}
          dataProvider={this._dataProvider.cloneWithRows(this.state.list)}
          rowRenderer={this._rowRenderer.bind(this)}
        />
      </View>
    );
  }
}

@naqvitalha Thanks anyways.

Hey @sksin28
Thanks for pointing out this issue but even after making the change you recommended, still the issue exists. I got a workaround(hack) for this but still didn't find a good solution.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

teryi picture teryi  路  5Comments

ErHarinderSingh picture ErHarinderSingh  路  6Comments

Komeyl94 picture Komeyl94  路  9Comments

jasonmerino picture jasonmerino  路  6Comments

leifermendez picture leifermendez  路  8Comments