reactjsreact-native

React why its rerender altough I use react.memo?


I have list with Recyclerlistview where you can click on a item then a modal is open and in the modal are items with flatlist. So if I open the modal its rerenders 2-3x. And if I scroll or click its rerenders again. But why ? I use react.memo

Recyclerlistview

import * as React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Image, Dimensions } from 'react-native';
import { RecyclerListView, LayoutProvider, DataProvider } from 'recyclerlistview';
import I18n from 'i18n-js';
import { Feather } from '@expo/vector-icons';
import { Portal } from 'react-native-portalize';
import { ModalUppinfo } from '../../modal/uppinfo/modalUppinfo';
import { ModalDrops } from '../../modal/drops/modalDrops';
import { map1boss, map2boss, otherboss } from '../../../utils/mockData';

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

const Bosses = ({ data }) => {
  const modals = Array.from({ length: 8 }).map(_ => React.useRef(null).current);

  const [uppRef, setUppRef] = React.useState([]);
  const [dropRef, setDropRef] = React.useState([]);

  const openModalDrop = React.useCallback((dropdata) => {
    setDropRef(dropdata);
    modals[3].open();
  }, [modals]);

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

  const dataProvider = React.useMemo(() => {
    return provider.cloneWithRows((data === 1) ? map1boss : (data === 2) ? map2boss : otherboss);
  }, [provider, map1boss, map2boss, otherboss]);

  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 Item = React.memo(({ name, image, level, translateID, upp, sell_price, dropinfo }) => (
    <View style={styles.item}>
      <Text style={styles.name}>{I18n.t(translateID)}</Text>
      <View style={{justifyContent: 'center', alignItems: 'center'}}>
        <Image source={image} resizeMode="contain" style={{height: 100, width: 100, marginBottom: 16 }} />
      </View>
      <View style={styles.itemFooter}>
        <TouchableOpacity onPress={() => openModalUpp(upp)} style={styles.infoBtn}>
          <Feather name="info" size={24} color="#333" />
          <Text>Uppinfo</Text>
        </TouchableOpacity>
        <TouchableOpacity onPress={() => openModalDrop(dropinfo)} style={styles.infoBtn}>
          <Feather name="info" size={24} color="#333" />
          <Text>Dropinfo</Text>
        </TouchableOpacity>
      </View>
    </View>
  ));

  const rowRenderer = (type, data) => {
    const { name, image, level, translateID, upp, sell_price, dropinfo } = data.item;
    return (
      <Item name={name} image={image} level={level} translateID={translateID} upp={upp} sell_price={sell_price} dropinfo={dropinfo} />
    )
  };

  return (
    <View style={styles.container}>
      <RecyclerListView 
        dataProvider={dataProvider}
        layoutProvider={layoutProvider}
        rowRenderer={rowRenderer}
        scrollViewProps={{showsVerticalScrollIndicator: false}}
        style={{flex: 1, marginTop: 20}}
      />
      <Portal>

        <ModalDrops DropInfo={dropRef} ref={el => (modals[3] = el)} />
      </Portal>
    </View>
  )
};

Modal Flatlist:

import * as React from 'react';
import { StyleSheet, Text, View, TouchableOpacity, Image, Dimensions } from 'react-native';
import { Modalize } from 'react-native-modalize';
import faker from 'faker';

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

export const ModalDrops = React.forwardRef(({ DropInfo }, ref) => {
  console.log('Drops')
  const Item = React.memo(({ name, image, index }) => {
    console.log('rerender');
    return (
      <View style={styles.item}>
        <TouchableOpacity style={{justifyContent: 'center', alignItems: 'center', paddingRight: DropInfo.monster.length -1 === index ? 36 : 0}}>
          <Image source={{uri: faker.image.avatar()}} resizeMode="contain" style={{height: 80, width: 80, borderRadius: 8, marginBottom: 10}} />
        </TouchableOpacity>
      </View>
    )
  });

  function renderItem({ item, index }) {
    return <Item name={item.name} image={item.image} index={index} />
  };

  function key() {
    return Math.random(10000).toString();
  }
  return (
    <Modalize
      ref={ref}
      modalHeight={300}
      flatListProps={{
        data: DropInfo.monster,
        style: {
          padding: 8,
          flex: 1
        },
        renderItem: renderItem,
        horizontal: true,
        ListEmptyComponent: () => {
          return (
            <View style={styles.itemEmpty}>
              <Text>Nicht Droppbar</Text>
            </View>
          )
        },
        keyExtractor: key,
        showsHorizontalScrollIndicator: false,
        scrollEventThrottle: 16,
        contentContainerStyle: {
          justifyContent: 'center',
          alignItems: 'center'
        }
      }}
    />
  )
});

Video: https://gyazo.com/ff91667f7f0258320317aa7645a046cf ............................................................................................................................................................


Solution

  • Issue

    From what I can see you've one major issue with the way you declare the Item component, it's declared inside another component, thus is it redeclared any time the outer component renders. This effectively unmounts the previous "instance" and mounts a new "instance". It isn't technically rerendering, it's just flat out remounted.

    Solution

    Don't declare React components inside other React components. In doing so and trying to memoize any render results won't work since each render cycle creates and mounts a new component. Move the Item declaration outside the components.

    Bosses

    const Item = React.memo(({ .... }) => (
      <View style={styles.item}>
        ...
        </View>
      </View>
    ));
    
    const Bosses = ({ data }) => {
      ...
    
      const rowRenderer = (type, data) => {
        const { name, image, level, translateID, upp, sell_price, dropinfo } = data.item;
        return (
          <Item
            name={name}
            image={image}
            level={level}
            translateID={translateID}
            upp={upp}
            sell_price={sell_price}
            dropinfo={dropinfo}
          />
        )
      };
    
      return (
        <View style={styles.container}>
          <RecyclerListView 
            dataProvider={dataProvider}
            layoutProvider={layoutProvider}
            rowRenderer={rowRenderer}
            scrollViewProps={{showsVerticalScrollIndicator: false}}
            style={{flex: 1, marginTop: 20}}
          />
          <Portal>
            <ModalDrops DropInfo={dropRef} ref={el => (modals[3] = el)} />
          </Portal>
        </View>
      )
    };
    

    ModalDrops

    const Item = React.memo(({ name, image, index }) => {
      return (
        <View style={styles.item}>
          ...
        </View>
      )
    });
    
    export const ModalDrops = React.forwardRef(({ DropInfo }, ref) => {
      function renderItem({ item, index }) {
        return <Item name={item.name} image={item.image} index={index} />
      };
    
      ...
    
      return (
        <Modalize
          ref={ref}
          modalHeight={300}
          flatListProps={{
            data: DropInfo.monster,
            style: {
              padding: 8,
              flex: 1
            },
            renderItem: renderItem,
            horizontal: true,
            ListEmptyComponent: () => {
              return (
                <View style={styles.itemEmpty}>
                  <Text>Nicht Droppbar</Text>
                </View>
              )
            },
            keyExtractor: key,
            showsHorizontalScrollIndicator: false,
            scrollEventThrottle: 16,
            contentContainerStyle: {
              justifyContent: 'center',
              alignItems: 'center'
            }
          }}
        />
      )
    });