Flipkart / recyclerlistview

High performance listview for React Native and web!
Apache License 2.0
5.21k stars 426 forks source link

Scroll offset not preserve when update re-render #581

Open teryi opened 3 years ago

teryi commented 3 years ago

Currently i face an issue whereby the scroll not preserve but jump to previous few item offset when re-render the view

naqvitalha commented 3 years ago

Can you share sample code?

teryi commented 3 years ago

Below is the code, the scroll keep offset to top when i change the checkbox state. Is there any other solutions?

import React, {Component} from "react";
import {Dimensions, TouchableOpacity} from "react-native";
import {RecyclerListView, DataProvider, LayoutProvider} from "recyclerlistview";
import {View, Body, Grid, Icon, ListItem, Right, Row, Button} from "native-base";
import Colors from "../constants/Colors";
import {TextInput} from "react-native-paper";
import {Text} from '../components/Themed';
import {useNavigation} from "@react-navigation/native";

const ViewTypes = {
    FULL: 0,
    HALF_LEFT: 1,
    HALF_RIGHT: 2
};

export default function RecycleTestComponent(props) {
    const navigation = useNavigation();
    const {mode, isShowCheckbox, data} = props;

    const [selected_leave, set_selected_leave] = React.useState([]);
    let dataProviderTemplate = new DataProvider((r1, r2) => {
        return r1 !== r2;
    });
    console.log(data)
    const [dataProvider, setDataProvider] = React.useState(dataProviderTemplate.cloneWithRows(data));

    React.useEffect(() => {
        set_selected_leave([])
    }, [isShowCheckbox])

    const isChecked = (itemId) => {
        const isThere = selected_leave.includes(itemId);
        return isThere;
    };

    const pressItem = ({leave_item}) => {
        if (!isShowCheckbox) {
            navigation.navigate('LeaveApprovalDetailScreen', {
                mode: mode,
                leave_item: leave_item,
            })
        } else {

            if (selected_leave.includes(leave_item.leave_appl_group_id)) {
                set_selected_leave(selected_leave.filter(item => item !== leave_item.leave_appl_group_id));

            } else {
                set_selected_leave(prevState => [...prevState, leave_item.leave_appl_group_id])
            }
        }
    }

    let {width} = Dimensions.get("window");
    const _layoutProvider = new LayoutProvider(
        index => {
            return ViewTypes.FULL;
        },
        (type, dim) => {
            dim.width = width;
            dim.height = 180;
        }
    );

    const _rowRenderer = React.useCallback((type, leave_item) => {
        if (leave_item) {
            let attachment_arr = []
            for (var i = 0; i < leave_item.leave_attachment.length; i++) {
                attachment_arr = attachment_arr.concat(leave_item.leave_attachment[i]);
            }
            return (
                <TouchableOpacity onPress={() => pressItem({leave_item})}>
                    <View noIndent style={{
                        width: Dimensions.get("window").width,
                        padding: 10,
                        height:180,
                        flexDirection: 'row',
                        borderBottomColor: Colors.lightgrey,
                        borderBottomWidth: 1,
                        backgroundColor: (isChecked(leave_item.leave_appl_group_id)) ? Colors.lightgrey : null
                    }}
                          key={leave_item.leave_appl_group_id}
                    >
                        {/*Left*/}
                        <View style={{flex: 1, justifyContent: 'center', alignContent: 'center',}}>
                            <Row>
                                <View style={{
                                    borderRadius: 4,
                                    backgroundColor: leave_status_color(leave_item.leave_status),
                                    padding: 4,
                                    height: 24
                                }}>
                                    <Text style={{
                                        fontSize: 12,
                                        color: Colors.milk_white,
                                        fontWeight: "bold"
                                    }}>{leave_item.leave_status_label}</Text>
                                </View>
                            </Row>
                            <Row>
                                <Text ellipsizeMode={'tail'} style={{fontSize: 14}}><Text
                                    style={{
                                        fontWeight: 'bold',
                                        color: Colors.darkblue
                                    }}>{leave_item.employee_no} {leave_item.employee_name} </Text> </Text>
                            </Row>

                            <Row>
                                <Text ellipsizeMode={'tail'} numberOfLines={1} style={{
                                    fontSize: 14,
                                    fontWeight: 'bold',
                                    color: Colors.darkgrey
                                }}>({leave_item.leave_code}) - {leave_item.leave_name}
                                </Text>
                            </Row>
                            <Row style={{marginBottom: 4}}>
                                <View style={{
                                    borderRadius: 4,
                                    borderWidth: 0.5,
                                    borderColor: Colors.grey,
                                    padding: 5,
                                    height: 26
                                }}>
                                    <Text style={{
                                        fontSize: 12,
                                        color: Colors.darkgrey,
                                        fontWeight: 'bold'
                                    }}>{leave_item.leave_start}</Text>
                                </View>
                                <View style={{justifyContent: 'center'}}><Text
                                    style={{
                                        fontSize: 12,
                                        color: Colors.darkgrey
                                    }}> until </Text></View>
                                <View style={{
                                    borderRadius: 4,
                                    borderWidth: 0.5,
                                    borderColor: Colors.grey,
                                    padding: 5,
                                    height: 26

                                }}>
                                    <Text style={{
                                        fontSize: 12,
                                        color: Colors.darkgrey,
                                        fontWeight: 'bold'
                                    }}>{leave_item.leave_end}</Text>
                                </View>
                            </Row>
                            <Row>
                                <View style={{
                                    borderRadius: 4,
                                    backgroundColor: Colors.blue,
                                    padding: 4,
                                    height: 24

                                }}>
                                    <Text style={{
                                        fontSize: 12,
                                        color: Colors.milk_white,
                                        fontWeight: "bold",
                                    }}>{leave_item.interval_format}</Text>
                                </View>
                            </Row>
                            <Row style={{marginTop: 4}}>
                                {
                                    (attachment_arr.length > 0) ?
                                        <View style={{alignSelf: 'center'}}>
                                            <Icon type={'AntDesign'} name={'paperclip'}
                                                  style={{
                                                      fontSize: 18,
                                                      padding: 2,
                                                      color: Colors.blue,
                                                  }}/>
                                        </View>

                                        : null
                                }

                                {
                                    (parseInt(leave_item.leave_emergency) == 1) ?
                                        <View style={{
                                            borderRadius: 4,
                                            backgroundColor: Colors.red,
                                            padding: 4,
                                            height: 24,
                                            marginRight: 4,
                                            marginTop: 4
                                        }}>
                                            <Text style={{
                                                fontSize: 12,
                                                color: Colors.milk_white,
                                                fontWeight: "bold"
                                            }}>Emergency</Text>
                                        </View>
                                        : null
                                }
                                {
                                    (parseInt(leave_item.leave_advance) == 1) ?
                                        <View style={{
                                            borderRadius: 4,
                                            backgroundColor: Colors.darkgrey,
                                            padding: 4,
                                            height: 24,
                                            marginRight: 4,
                                            marginTop: 4
                                        }}>
                                            <Text style={{
                                                fontSize: 12,
                                                color: Colors.milk_white,
                                                fontWeight: "bold"
                                            }}>Advance</Text>
                                        </View>
                                        : null
                                }

                            </Row>
                        </View>
                        {/*Right*/}
                        <View style={{width: 30}}>
                            <Row>
                                <View>
                                    <Text><Icon type="MaterialCommunityIcons"
                                                name={(isChecked(leave_item.leave_appl_group_id)) ? "checkbox-marked" : "checkbox-blank-outline"}
                                                style={{color: Colors.darkgrey}}/></Text>
                                </View>

                            </Row>
                            <Row>
                                <View style={{
                                    alignItems: 'center',
                                    alignContent: 'center',
                                    justifyContent: 'center',
                                    width: 30,
                                    height: 30,
                                    backgroundColor: Colors.blue,
                                    borderRadius: 4,
                                }}>
                                    <Icon type={'Ionicons'} name={'md-chatbox-sharp'}
                                          style={{
                                              fontSize: 20,
                                              color: Colors.milk_white,
                                          }}/>
                                </View>
                            </Row>
                        </View>
                    </View>
                </TouchableOpacity>
            )
        }
    }, [selected_leave])

    return <RecyclerListView forceNonDeterministicRendering={true} layoutProvider={_layoutProvider}
                             dataProvider={dataProvider}
                             rowRenderer={_rowRenderer}/>;
}

const leave_status_color = (leave_status) => {
    switch (parseInt(leave_status)) {
        case 3:
        case 7:
        case 8:
            return Colors.orange
            break;
        case 5:
            return Colors.red
            break;
        default:
            return Colors.blue
    }
}

https://user-images.githubusercontent.com/32726353/103963374-0a9cf400-5194-11eb-88b8-b9ede895e459.mp4

timurridjanovic commented 3 years ago

I have the same issue.. when I modify local state, it messes up the scroll index and also the dynamic heights if I use forceDeterministicRendering and don't set a dim.height on the cells..

IoanaBdn commented 3 years ago

I had the same issue, but I managed to resolve it with the answers I find in here: issue-493 From what I see in your code, you should useState even for layout provider, not only for data.

ammarahm-ed commented 3 years ago

After hours of bumping my head against the wall and trying everything possible, I was finally able to fix the issue. The problem most people are facing is when updating a value in their data via state causing a complete rerender.

After creating your layoutProvider disable the Undocumented shouldRefreshWithAnchoring property:

 _layoutProvider.shouldRefreshWithAnchoring = false;

I had the jumping issue because I have to recreate layoutProvider on rerender because If I don't, when I insert or move items, they take up wrong type and render into wrong component. I rewrote code multiple times, rewrote it as a class component etc but it would always jump to the start of top most item because shouldRefreshWithAnchoring means that when a rerender occurs, list should scroll to first visible item in the list even if it is 1px in View.

Here's what happened when I used a fixed layoutProvider that never changed:

Before Render: type section -> section header type note -> note type note -> note type section -> section header

After rerender and changing item(Pin a note to top of list): type section -> note type note -> note type note -> section header type section -> note

This won't be a problem for you if all your items are of the same type and you can keep a fixed layoutProvider created once.

Another note that I am using Functional Components. I read somewhere that you should not recreate the layoutProvider but when I do that, on removing or moving items, they are rendered in a wrong types. For example a note would render in place of section. Hope this helps you and saves you some time.

Finally I think this is a great library but lacks when it comes to proper documentation. I even watched a couple of videos on it too which again only showed static list items rendered using a generator function which doesn't help

arsaey commented 2 years ago

After hours of bumping my head against the wall and trying everything possible, I was finally able to fix the issue. The problem most people are facing is when updating a value in their data via state causing a complete rerender.

After creating your layoutProvider disable the Undocumented shouldRefreshWithAnchoring property:

 _layoutProvider.shouldRefreshWithAnchoring = false;

I had the jumping issue because I have to recreate layoutProvider on rerender because If I don't, when I insert or move items, they take up wrong type and render into wrong component. I rewrote code multiple times, rewrote it as a class component etc but it would always jump to the start of top most item because shouldRefreshWithAnchoring means that when a rerender occurs, list should scroll to first visible item in the list even if it is 1px in View.

Here's what happened when I used a fixed layoutProvider that never changed:

Before Render: type section -> section header type note -> note type note -> note type section -> section header

After rerender and changing item(Pin a note to top of list): type section -> note type note -> note type note -> section header type section -> note

This won't be a problem for you if all your items are of the same type and you can keep a fixed layoutProvider created once.

Another note that I am using Functional Components. I read somewhere that you should not recreate the layoutProvider but when I do that, on removing or moving items, they are rendered in a wrong types. For example a note would render in place of section. Hope this helps you and saves you some time.

Finally I think this is a great library but lacks when it comes to proper documentation. I even watched a couple of videos on it too which again only showed static list items rendered using a generator function which doesn't help

worked for me. you save my day. thanks