react native recyclerlistview if state changed its scroll to top

Viewed 1129

If I change any state or change the textinput value, if it changed then its scroll to top and leave the textinput. Or If I like one product, its autoamticlly scroll to top if the array is changed. Anyone can help me ?

import React, { useState, useMemo, useEffect, forwardRef, memo } from 'react';
import { StyleSheet, Text, TextInput, View, TouchableOpacity, Image, Dimensions } from 'react-native';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
import { RecyclerListView, LayoutProvider, DataProvider } from 'recyclerlistview';
import { useSelector, useDispatch } from 'react-redux';
import { AntDesign } from '@expo/vector-icons';
import { addAmountOnCartItem } from '../../../redux/slice/product/shopping_cart';
import faker from 'faker';
import ButtonWithoutLoader from '../../ButtonWithoutLoader';

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

const Shopping_cart_list = ({ datas, onPressSetting }) => {
  const dispatch = useDispatch();

  const provider = useMemo(() => {
    return new DataProvider(
      (r1, r2) => {
        return r1 !== r2;
      },
      index => {
        return 'id:' + index;
      }
    )
  }, []);

  const dataProvider = useMemo(() => {
    return provider.cloneWithRows(datas);
  }, [datas, provider]);

  const [update, updateRecycler] = useState({
    update: false
  });

  const handleChange = (e, product_id) => {
    dispatch(addAmountOnCartItem({ value: e, product_id }));
    updateRecycler(prevState => {
      return {
        update: !prevState
      }
    });
  };

  const layoutProvider = new LayoutProvider((i) => {
    return dataProvider.getDataForIndex(i).type;
  }, (type, dim) => {
    switch(type) {
      case 'NORMAL':
        dim.height = 250;
        dim.width = width * 0.9;
      break;
      default:
        dim.height = 0;
        dim.width = 0;
      break;
    }
  });

  const RenderData = memo(({ product_id, product_name, price, product_image, amount, username }) => {
    return (
      <TouchableWithoutFeedback style={{height: 250, backgroundColor: '#fff', marginBottom: 16}}>
        <View style={styles.header}>
          <TouchableOpacity style={styles.profile_info}>
            <Image source={{uri: faker.image.avatar()}} resizeMode="contain" style={styles.profile_image} />
            <Text style={styles.username}>{ username }</Text>
          </TouchableOpacity>
          <TouchableOpacity onPress={() => onPressSetting(product_id)}>
            <AntDesign name="setting" size={24} color="#444" />
          </TouchableOpacity>
        </View>
        <View style={styles.mainContainer}>
          <Image source={{uri: product_image}} style={styles.image} />
          <View>
            <Text style={styles.text}>{product_name}</Text>
            <Text style={styles.text}>Preis: {price}</Text>
            <View style={{flexDirection: 'row', alignItems: 'center'}}>
              <Text style={styles.text}>Anzahl: </Text>
              <AntDesign name="minussquareo" style={{marginRight: 6}} size={24} color="black" />
              <TextInput onBlur={() => handleChange(1, product_id)} value={ isNaN(amount) ? '' : amount.toString() } onChangeText={e => handleChange(e, product_id)} style={{height: 28, width: 28, borderRadius: 4, textAlign: 'center', backgroundColor: '#eee'}} />
              <AntDesign name="plussquareo" style={{marginLeft: 6}} size={24} color="black" />
            </View>
          </View>
        </View>
        <View style={[styles.header, { marginTop: 4 }]}>
          <ButtonWithoutLoader onPress={() => updateRecycler(prevState => !prevState)} title="Jetzt Kaufen!" width={width * 0.9} />
        </View>
      </TouchableWithoutFeedback>
    )
  });

  const rowRenderer = (type, data) => {
    const { product_id, product_name, price, product_image, amount, username } = data.item;
    return <RenderData product_id={product_id} product_name={product_name} price={price} product_image={product_image} amount={amount} username={username} />
  };

  return (
    <View style={{flex: 1, paddingBottom: 85}}>
      <RecyclerListView
        dataProvider={dataProvider}
        layoutProvider={layoutProvider}
        forceNonDeterministicRendering={true}
        rowRenderer={rowRenderer}
        style={{marginLeft: width * 0.05, marginRight: width * 0.05}}
        extendedState={update}
        scrollViewProps={{showsVerticalScrollIndicator: false}}
      />
    </View>
  )
};

Flatlist:

import React, { useState, useRef, memo, useMemo } from 'react';
import { StyleSheet, Animated, FlatList, Text, TextInput, View, TouchableOpacity, Image, Dimensions } from 'react-native';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
import { useSelector, useDispatch } from 'react-redux';
import { useNavigation } from '@react-navigation/core';
import { AntDesign } from '@expo/vector-icons';
import { addAmountOnCartItem } from '../../../redux/slice/product/shopping_cart';
import faker from 'faker';
import ButtonWithoutLoader from '../../ButtonWithoutLoader';

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

const Shopping_cart = ({ datas, onPressSetting }) => {
  const dispatch = useDispatch();
  const navigation = useNavigation();

  const [update, updateRecycler] = useState({
    update: false
  });

  const handleChange = (e, product_id) => {
    dispatch(addAmountOnCartItem({ value: e, product_id }));
    updateRecycler(prevState => {
      return {
        update: !prevState
      }
    });
  };

  const RenderItem = memo(({ item }) => {
    const { product_id, product_name, price, product_image, amount, username, size, colors, desc, selectedColor, selectedSize } = item.item;
    return (
      <TouchableWithoutFeedback style={{height: 300, backgroundColor: '#fff', marginBottom: 16}}>
        <View style={styles.header}>
          <TouchableOpacity style={styles.profile_info}>
            <Image source={{uri: faker.image.avatar()}} resizeMode="contain" style={styles.profile_image} />
            <Text style={styles.username}>{ username }</Text>
          </TouchableOpacity>
          <TouchableOpacity onPress={() => onPressSetting(product_id)}>
            <AntDesign name="setting" size={24} color="#444" />
          </TouchableOpacity>
        </View>
        <TouchableOpacity onPress={() => navigation.navigate('ProductStack', {
                product_id,
                desc,
                price,
                product_image,
                username,
                user_profil_image: faker.image.avatar(),
                size,
                colors,
              })} style={styles.mainContainer}>
          <Image source={{uri: product_image}} style={styles.image} />
          <View>
            <Text style={styles.text}>{product_name}</Text>
            <Text style={styles.text}>Preis: {price}</Text>

            <Text style={styles.text}>Farbe: {selectedColor}</Text>
            <Text style={styles.text}>Größe: {selectedSize}</Text>
            <View style={{flexDirection: 'row', alignItems: 'center'}}>
                <Text style={styles.text}>Anzahl: </Text>
                <AntDesign name="minussquareo" style={{marginRight: 6}} size={24} color="black" />
                <TextInput onBlur={() => handleChange(1, product_id)} value={ isNaN(amount) ? '' : amount.toString() } onChangeText={e => handleChange(e, product_id)} style={{height: 28, width: 28, borderRadius: 4, textAlign: 'center', backgroundColor: '#eee'}} />
                <AntDesign name="plussquareo" style={{marginLeft: 6}} size={24} color="black" />
            </View> 
          </View>
        </TouchableOpacity>
        <View style={[styles.header, { marginTop: 4 }]}>
          <ButtonWithoutLoader onPress={() => updateRecycler(prevState => !prevState)} title="Jetzt Kaufen!" width={width * 0.9} />
        </View>
      </TouchableWithoutFeedback>
    );
  });
  return (
    <FlatList 
      data={datas}
      keyExtractor={item => item.item.product_id + Math.random(100)}
      renderItem={({ item }) => <RenderItem item={item}/>}
      contentContainerStyle={{justifyContent: 'center', alignItems: 'center'}}
      removeClippedSubviews={true}
      initialNumToRender={2}
      maxToRenderPerBatch={1}
      extraData={update}
      updateCellsBatchingPeriod={100}
    />
  );
};

........................................................................................................................................................................................

2 Answers

If you see the docs of RecyclerView, you will see for extendedState prop:

In some cases the data passed at row level may not contain all the info that the item depends upon, you can keep all other info outside and pass it down via this prop. Changing this object will cause everything to re-render. Make sure you don't change it often to ensure performance. Re-renders are heavy.

Based on your code you are updating this prop in button click and text change handler, this will eventually re-render your screen as per the description. So, here you would need to revise your logic to avoid unnecessary re-renders.

I recently just solved this problem in my own project so I'd be glad to help.

The main issue is that you can't have ANY UI related state in your list items. None. You must instead move all of that information to be transferred through props.

I see your only instance of that is through your TextInput. This could be difficult. So my first question would then be: do you really need RecyclerListView? Unless your list is thousands of items long, and items have a complicated/image intensive UI, I'd suggest FlatList for this project.

If you do continue to need RecyclerListView, I'd suggest making it so after a textinput is finished ('finished' as in maybe after a 3 second timer or, when they press 'submit' or something like that), you change the dataProvider's data object. Change the item at the index you need, and then resubmit it with cloneWithRows()

Related