Recyclerlistview: List is not updated/rerendered unless scrolled far away and back

Created on 11 Aug 2018  路  11Comments  路  Source: Flipkart/recyclerlistview

I'm trying to display a checkbox alongside each row in the list. Upon clicking on the checkbox, I want it to -be selected/de-selected. I do this by updating the array element property. However, the row is not updated until I scroll far away and then scroll back. I also tried the extendedState prop but that did not help too.

checkboxSelected(item) {
    item.selected = !item.selected
}

<Checkbox checked={!!item.selected} onPress={this.checkboxSelected.bind(this, item)} />

Any help would be much appreciated. Thanks

Most helpful comment

@sksin28 The problem is, you're mutating the object directly, instead of making a new object. That means that the reference is the same

listItem.selected = !listItem.selected

And when the rowHasChanges runs, it won't see that there are any changes.

const row1 = {
  label: 'First Row',
  selected: false
}

// This is where the issue is
row1.selected = true;

Instead, you should be making a new object

const row 2 = {
  ...row,
  selected: true
}

So, in your code:

this.setState(prevState => {
  return {
    list: _.map(prevState.list, listItem => {
       if (listItem.name === item.name) {
         // Here, you should return a new object.
         return {
            ...listItem,
            listItem.selected = !listItem.selected
           };
        }
        return listItem;
    })
  };
});

All 11 comments

Shouldn't you be using setState or dispatching an action instead of mutating the object?
item.selected = !item.selected

Same problem.. ..still searching for solution..

I did try mutating the state but it did not help.

constructor(props) {
        super(props);
        let { width } = Dimensions.get('window');

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

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

        this._rowRenderer = this._rowRenderer.bind(this);

        this.state = {
            list,
            dataProvider: this._dataProvider.cloneWithRows(list)
        };
    }
    //Given type and data return the view component
    _rowRenderer(type, item) {
        return (
            <View>
                <CardItem>
                <Left>
                    <Body
                        style={{
                            height: 100,
                            justifyContent: 'center'
                        }}
                    >
                        <Text>{item.name}</Text>
                    </Body>
                </Left>
                <Right>{this.renderCheckbox(item)}</Right>
                </CardItem>
            </View>
        );
    }
    render() {
        return (
            <RecyclerListView
                layoutProvider={this._layoutProvider}
                dataProvider={this.state.dataProvider.cloneWithRows(this.state.list)}
                rowRenderer={this._rowRenderer}
                extendedState={this.state.selectedJson}
            />
        );
    }

    checkboxSelect(item) {
         let list = this.state.list;
        _.map(list, listItem => {
            if (listItem.name === item.name) {
                listItem.selected = !listItem.selected
            }
            return listItem;
        });

                 this.setState(prevState => {
                     return {
                     list,
                    dataProvider: prevState.dataProvider.cloneWithRows(list)
                     };
                });
     }

Your rowHasChanged implementation will return false here. You should maintain list of selections in extended state and let the component consume it.

Can you please explain how rowHasChanged function works? Do the r1, r2 parameters correspond to previous and current values of row? If that's the case, changing the changing the selected property should cause it to return 'true`.

I also tried maintaining the list of selected items in a separate object and pass it to the extendedState props. However that didn't work too. The checkbox is only selected after scrolling away and back. So I think the rowHasChanged function is working fine but it's being evaluated only after the scrolling.

    _rowRenderer(type, item) {
        return (
            <View>
                <CardItem>
                <Left>
                    <Body
                        style={{
                            height: 100,
                            justifyContent: 'center'
                        }}
                    >
                        <Text>{item.name}</Text>
                    </Body>
                </Left>
                <Right>
                       <CheckBox
                               checked={!!this.state.selectedJson[item.slug]}
                               onPress={this.checkboxSelect.bind(this, item)}
                        />
                </Right>
                </CardItem>
            </View>
        );
    }

    render() {
        return (
            <RecyclerListView
                layoutProvider={this._layoutProvider}
                dataProvider={this.state.dataProvider.cloneWithRows(this.state.list)}
                rowRenderer={this._rowRenderer}
                extendedState={this.state.selectedJson}
            />
        );
    }

    checkboxSelect(item) {
         let list = this.state.list;
        _.map(list, listItem => {
            if (listItem.name === item.name) {
                listItem.selected = !listItem.selected
            }
            return listItem;
        });
        let selectedJson = this.state.selectedJson;
        selectedJson[item.name] = !selectedJson[item.name];

       this.setState(prevState => {
             return {
                 list,
                 selectedJson,
                 dataProvider: prevState.dataProvider.cloneWithRows(list)
             };
        });
     }

@sksin28 The problem is, you're mutating the object directly, instead of making a new object. That means that the reference is the same

listItem.selected = !listItem.selected

And when the rowHasChanges runs, it won't see that there are any changes.

const row1 = {
  label: 'First Row',
  selected: false
}

// This is where the issue is
row1.selected = true;

Instead, you should be making a new object

const row 2 = {
  ...row,
  selected: true
}

So, in your code:

this.setState(prevState => {
  return {
    list: _.map(prevState.list, listItem => {
       if (listItem.name === item.name) {
         // Here, you should return a new object.
         return {
            ...listItem,
            listItem.selected = !listItem.selected
           };
        }
        return listItem;
    })
  };
});

Thanks @AbdallaMohamed @naqvitalha . It worked. :)

@sksin28 can you send me whole page. I m still not able to figure out. please help

@rasanu This is how I got it going in my case.

class MyList extends Component {
    constructor(props) {
        super(props);
        let list = [{name:'a', selected:false}, {name:'b', selected:false}, {name:'c', selected:false}];

        const { width } = Dimensions.get('window');


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

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

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

    _rowRenderer(type, item) {
        return (
            <View>
                <CardItem>
                    <Body>
                        <Text>{item.name}</Text>
                    </Body>
                    <Right>
                        <CheckBox checked={!!item.selected} onPress={this.checkboxSelect.bind(this, item)} />
                    </Right>
                </CardItem>
            </View>
        );
    }

    checkboxSelect(item) {
        this.setState(prevState => {
            return {
                list: _.map(prevState.list, listItem => {
                    if (listItem.name === item.name) {
                        return {
                            ...listItem,
                            selected: !listItem.selected
                        };
                    }
                    return listItem;
                })
            };
        });
    }

    render() {
        return (
            <RecyclerListView
                layoutProvider={this._layoutProvider}
                dataProvider={this.state.dataProvider.cloneWithRows(this.state.list)}
                rowRenderer={this._rowRenderer.bind(this)}
            />
        );
    }
}

@sksin28 thnx... .. by analysing from previous post code i did it yesterday ... By the way thankx.. for replying bro馃槉

Hello there !! i am new in react native and i am using recyclerlistview. i am trying to update my row in real time but it is not working unless i scroll down and then go up. Please help me out here. i will be very thankful !!

Here is the file--

Demo.zip

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ErHarinderSingh picture ErHarinderSingh  路  6Comments

H-Shafiei picture H-Shafiei  路  8Comments

andrea7887 picture andrea7887  路  6Comments

waheedakhtar694 picture waheedakhtar694  路  6Comments

abrenoch picture abrenoch  路  6Comments