Mike
Mike

Reputation: 12829

RecyclerListView scrolls to top onEndReached with functional component

I have implemented a RecyclerListView(Flipkart Github) using Redux as seen below. Everything seems to be working great except when onEndReached is called and a new batch of data comes through, the list gets positioned to the top of the page rather than remaining smooth. See that behavior in the GIF below:

Note: This is happening on web(chrome). I tried the latest stable and 2.0.13-alpha.1

import React, { useCallback } from 'react';
import { View, Text, Dimensions } from 'react-native';
import { useSelector, useDispatch } from 'react-redux';
import { RecyclerListView, DataProvider, LayoutProvider } from 'recyclerlistview/web';
import { createSelector } from 'reselect';

import { loadData } from '../actions';

const selectData = createSelector(
  state => state.data,
  data => Object.values(data),
);

let containerCount = 0;
class CellContainer extends React.Component {
  constructor(args) {
    super(args);
    this._containerId = containerCount++;
  }
  render() {
    return (
      <View {...this.props}>
        {this.props.children}
        <Text>Cell Id: {this._containerId}</Text>
      </View>
    );
  }
}

const List = ({ isServer }) => {
  let { width } = Dimensions.get('window');

  const dispatch = useDispatch();

  const data = useSelector(selectData);

  const dataDataProvider = new DataProvider((r1, r2) => {
    return r1 !== r2;
  }).cloneWithRows(data);

  const onEndReached = useCallback(() => dispatch(loadData()), [dispatch]);

  const layoutProvider = new LayoutProvider(
    () => 0,
    (type, dim) => {
      dim.width = width;
      dim.height = 240;
    },
  );

  const rowRenderer = (type, data) => {
    return <CellContainer />;
  };

  return (
    <View
      style={{
        display: "flex",
        flex: 1,
        width: "100vw",
        height: "100vh"
      }}
    >
      <RecyclerListView
        layoutProvider={layoutProvider}
        dataProvider={dataDataProvider}
        onEndReached={onEndReached}
        rowRenderer={rowRenderer}
      />
    </View>
  );
};

export default List;

UPDATE: I can confirm that the class-based version works with no issues using redux connect. Leaning towards this being some kind of incompatibility with the library. Interesting nonetheless.

Below snippet is a simplified working example of this demo https://codesandbox.io/s/k54j2zx977

class App extends React.Component {
  constructor(props) {
    let { width } = Dimensions.get('window');

    super(props);
    this.state = {
      dataProvider: new DataProvider((r1, r2) => {
        return r1 !== r2;
      }),
      layoutProvider: new LayoutProvider(
        index => 0,
        (type, dim) => {
          dim.width = width;
          dim.height = 240;
        },
      ),
      count: 0,
    };
  }

  componentDidUpdate(prevProps) {
    if (this.props.data.length !== prevProps.data.length) {
      this.setState({
        dataProvider: this.state.dataProvider.cloneWithRows(this.props.data),
        count: this.props.data.length
      });
    }
  }

  componentWillMount() {
    this.props.loadData();
  }

  async fetchMoreData() {
    this.props.loadData();
  }

  rowRenderer = (type, data) => {
    //We have only one view type so not checks are needed here
    return <CellContainer />;
  };

  handleListEnd = () => {
    this.fetchMoreData();
    //This is necessary to ensure that activity indicator inside footer gets rendered. This is required given the implementation I have done in this sample
    this.setState({});
  };

  render() {
    //Only render RLV once you have the data
    return (
      <View style={styles.container}>
        {this.state.count > 0 ? (
        <RecyclerListView
          style={{ flex: 1 }}
          contentContainerStyle={{ margin: 3 }}
          onEndReached={this.handleListEnd}
          dataProvider={this.state.dataProvider}
          layoutProvider={this.state.layoutProvider}
          renderAheadOffset={0}
          rowRenderer={this.rowRenderer}
        />
        ) : null}
      </View>
    );
  }
}

export default connect(
  state => ({
    data: selectData(state),
  }),
  dispatch => bindActionCreators({ loadData }, dispatch),
)(App);

Upvotes: 5

Views: 5821

Answers (2)

Fish
Fish

Reputation: 571

Just add the below line of code after your layoutProvider. The shouldRefreshWithAnchoring property of layoutProvider prevents recycler view list to not refresh the existing rows if more rows are added to the list.

layoutProvider.shouldRefreshWithAnchoring = false;

Upvotes: 4

user7532779
user7532779

Reputation: 213

Solution is here: https://github.com/Flipkart/recyclerlistview/issues/449

Just add

  const [layoutProvider] = React.useState(
new LayoutProvider(
  (index) => 1,
  (type, dim) => {
    dim.width = ITEM_HEIGHT
    dim.height = ITEM_HEIGHT
  }
)

)

Upvotes: 0

Related Questions