FlatList React Native перерисовывается при добавлении элемента в нормализованное и отсортированное хранилище Redux.

Когда я добавляю sortComparer в createEntityAdapter для сортировки списка по дате создания, любое добавление/удаление объектов приводит к обновлению всех элементов в моем списке. Если я удалю sortComparer, он будет работать нормально. Это происходит, хотя я запоминаю компоненты и функции.

Это мой редуктор, я использую setExpense для добавления/обновления расходов:

      import {
  createAction,
  createAsyncThunk,
  createEntityAdapter,
  createSelector,
  createSlice,
} from '@reduxjs/toolkit';

const baseName = 'expenses';

const adapter = createEntityAdapter({
  selectId: expense => expense.id,
  sortComparer: (a, b) => b.created_at.localeCompare(a.created_at),
});

const initialState = adapter.getInitialState({
  ...
});

export const expensesSlice = createSlice({
  name: baseName,
  initialState,
  reducers: {
    ...
    //ATUALIZAR/ADICIONAR DESPESA
    setExpense(state, action) {
      adapter.upsertOne(state, {
        ...action.payload,
        sync: true,
        sync_error: null,
      });
    },
    ...
  },
  extraReducers: ...
  },
});

...

export const {
  selectAll: selectAllExpenses,
  selectIds: selectExpensesIds,
  selectById: selectExpenseById,
  selectEntities: selectExpensesEntities,
  selectTotal: selectExpensesTotal,
} = adapter.getSelectors(state => state.expenses);

export default expensesSlice.reducer;

export const selectSingleExpensesIds = createSelector(
  [selectExpensesIds, selectExpensesEntities],
  (ids, entities) => {
    return ids.filter(
      id => !entities[id].request_id && !Boolean(entities[id].deleted),
    );
  },
);

Вот мой экран, на котором я отображаю список:

      import { useCallback, useEffect, useMemo, useRef } from 'react';
import { StyleSheet, View, VirtualizedList } from 'react-native';
import FocusRender from 'react-navigation-focus-render';
import { useDispatch, useSelector } from 'react-redux';
import ExpenseItem from '../../components/list-items/expense/ExpenseItem';
import {
  selectSingleExpensesIds,
  syncExpenses
} from '../../store/reducers/expensesSlice';
import { onChangeSelection } from '../../store/reducers/selectionSlice';

const ItemSeparator = () => {
  return <View style={styles.separator} />;
};

export default function (props) {
  const {
    navigation
  } = props;

  const dispatch = useDispatch();

  const expensesIds = useSelector(selectSingleExpensesIds);

  const refreshing = useSelector(state => state.expenses.working);

  const {type, selected} = useSelector(state => state.selection);

  const isSelectionModeRef = useRef();

  const selectedNum = useMemo(() => Object.keys(selected).length, [selected]);

  useEffect(() => {
    const bool = Boolean(type === 'SingleExpenses' && selectedNum > 0);
    isSelectionModeRef.current = bool;
  }, [type, Boolean(selectedNum > 0)]);

  const toggleSelection = useCallback(expenseId => {
    dispatch(
      onChangeSelection({
        type: 'SingleExpenses',
        action: 'toggle',
        payload: expenseId,
      }),
    );
  }, []);

  const handlePress = useCallback((expenseId, isRoute) => {
    //Navegar para respectiva tela da despesa
    if (isRoute) {
    } else {
      navigation.navigate('ExpenseView', {expenseId});
    }
  }, []);

  const renderExpenseItem = useCallback(({item, index}) => {
    return (
      <ExpenseItem
        key={`${item}-${index.toString()}`}
        expenseId={item}
        isSelectionModeRef={isSelectionModeRef}
        selectionType="SingleExpenses"
        onSelect={toggleSelection}
        onPress={handlePress}
      />
    );
  }, []);

  const keyExtractor = useCallback((item, index) => {
    return item;
  }, []);

  const getItemLayout = useCallback(
    (data, index) => ({length: 80 + 3, offset: (80 + 3) * index, index}),
    [],
  );

  const getItemCount = useCallback(data => (data || []).length, []);

  const getItem = useCallback((_data, index) => _data[index], []);

  const handleRefresh = useCallback(() => {
    dispatch(syncExpenses());
  }, []);

  return (
    <FocusRender>
      <View style={styles.container}>
        <VirtualizedList
          refreshing={refreshing}
          onRefresh={handleRefresh}
          style={styles.container}
          data={expensesIds}
          getItemCount={getItemCount}
          getItem={getItem}
          initialNumToRender={10}
          renderItem={renderExpenseItem}
          keyExtractor={keyExtractor}
          contentContainerStyle={styles.listContentContainer}
          ItemSeparatorComponent={<ItemSeparator />}
          removeClippedSubviews
          getItemLayout={getItemLayout}
        />
      </View>
    </FocusRender>
  );
}

const styles = StyleSheet.create({
  ...
});

Вот элемент списка:

      import React, { memo, useCallback } from 'react';
import { Pressable, View } from 'react-native';
import { TouchableRipple } from 'react-native-paper';
import { connect, useSelector } from 'react-redux';
import { useTheme } from '../../../context/ThemeContext';
import { selectExpenseById } from '../../../store/reducers/expensesSlice';
import { expenseItemData } from '../../../utils/ initialStates';
import Content from './components/Content';
import ErrorIndicator from './components/ErrorIndicator';
import LeftContent from './components/LeftContent';
import expenseItemStyles from './styles';

const ExpenseItem = ({
  expenseId,
  selected,
  onSelect,
  onPress,
  isSelectionModeRef,
}) => {
  const {
    ...
  } = useSelector(
    state => selectExpenseById(state, expenseId) || expenseItemData,
  );

  const {
    isDarkTheme,
    theme: {
      colors: {
        elevation: {level2},
      },
    },
  } = useTheme();

  const handleSelect = useCallback(
    e => {
      onSelect(expenseId);
    },
    [expenseId, onSelect],
  );

  const handleLongPress = useCallback(() => {
    handleSelect();
  }, [handleSelect]);

  const handlePress = useCallback(
    e => {
      if (isSelectionModeRef?.current) {
        onSelect(expenseId);
        return;
      }

      onPress(expenseId, is_route);
    },
    [onPress, expenseId, is_route, onSelect],
  );

  console.log('re-render');

  return (
    <TouchableRipple
      rippleColor={isDarkTheme ? 'rgba(255,255,255,.04)' : 'rgba(0,0,0,.06)'}
      style={[expenseItemStyles.touchContainer]}
      delayLongPress={200}
      onLongPress={handleLongPress}
      onPress={handlePress}>
      <View
        style={[
          expenseItemStyles.container,
          {backgroundColor: selected ? level2 : 'transparent'},
        ]}>
        <Pressable
          disabled={Boolean(sync)}
          onPress={handleSelect}
          onLongPress={() => console.log('Ver comprovante')}>
          <LeftContent
            syncError={Boolean(sync_error)}
            sync={Boolean(sync)}
            selected={selected}
            isRoute={is_route}
            receipt={receipts[0]}
          />
        </Pressable>
        <Content
          notes={notes || ''}
          amount={amount_converted || amount}
          date={date}
          currency={currency}
          isRoute={is_route}
          route={{
            distance: distance || 0,
            from: from || '',
            to: to || '',
          }}
          expenseTypeId={type_id}
          refundable={refundable}
          syncError={Boolean(sync_error)}
        />
        {Boolean(sync_error) && (
          <ErrorIndicator errorMessage={sync_error || ''} />
        )}
      </View>
    </TouchableRipple>
  );
};

function mapStateToProps(state, ownProps) {
  const {expenseId, selectionType} = ownProps;
  const selected = Boolean(
    state.selection.type &&
      state.selection.type === selectionType &&
      state.selection.selected[expenseId],
  );
  return {
    selected,
    expenseId,
    ...ownProps,
  };
}

function expensePropsAreEqual(prevExpense, nextExpense) {
  return prevExpense.selected === nextExpense.selected;
}

export default connect(mapStateToProps)(
  memo(ExpenseItem, expensePropsAreEqual),
);

Я ожидал, что элементы будут нормально отображаться в соответствии с вашими изменениями, независимо от порядка данных.

0 ответов

Другие вопросы по тегам