React Native: запретить ре-рендеринг дочернего компонента

Рейтинг: 0Ответов: 0Опубликовано: 21.06.2023

У меня есть такой компонент

import {
  Pressable,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';
import { CustomIcon } from '../customIcon/CustomIcon';
import Children from './Children';
import {
  BLACK,
  GRAY,
  OPACITY_PINK,
  PINK,
  PINK_OPACITY,
  PINK_RED,
  WHITE,
} from '../../styles/colors';
import React, { memo, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import {
  setCart,
  setSelectedServices,
  setTotal,
} from '../../store/slices/orderSlice';
import axios from 'axios';
import { API_URL } from '../../modules/constants';
import { useMutation } from '@apollo/client';
import { REMOVE_FROM_CART } from '../../graphql/Mutation';

const OneAccordion = memo(({ item, index, childElement, masterId }) => {
  const [showChild, setShowChild] = useState(false);
  const { t } = useTranslation();
  const selected = useSelector(state => state.order.selectedService);
  const cart = useSelector(state => state.order.serviceCart);
  const dispatch = useDispatch();
  const token = useSelector(state => state.user.accessToken);
  const [removeFromCart] = useMutation(REMOVE_FROM_CART);

  useEffect(() => {
    const sel = [];
    if (cart) {
      cart.map(c => {
        sel.push(Number(c.service.id));
      });
    }
    dispatch(setSelectedServices(sel));
  }, [cart]);

  const addToCart = service => {
    if (selected.includes(Number(service.id))) {
      removeFromCart({
        variables: { master_id: masterId, service_id: service.id },
      }).then(response => {
        dispatch(setCart(response.data.removeFromCart));
        let t = 0;
        response.data.removeFromCart.map(e => {
          t += Number(e.price);
        });
        dispatch(setTotal(t));
      });
    } else {
      axios.defaults.headers.common = {
        Authorization: `Bearer ${token}`,
      };
      axios({
        url: API_URL,
        method: 'post',
        data: {
          query: `
          mutation {
            addToCart(service_id:${service.id}, master_id:${masterId}, count: 1) {
              id
              service {
                id
                name
              }
              user_id
              price
            }
          }`,
        },
      })
        .then(result => {
          dispatch(setCart(result.data.data.addToCart));
          let t = 0;
          result.data.data.addToCart.map(e => {
            t += Number(e.price);
          });
          dispatch(setTotal(t));
        })
        .catch(error => {
          console.log('[Error]', error);
        });
    }
  };

  return (
    <View key={index}>
      <TouchableOpacity
        key={index}
        style={css.header}
        onPress={() => setShowChild(!showChild)}>
        <Text style={css.headerTitle}>
          {item.title ? item.title : item.name}
        </Text>
        <CustomIcon icon={item.collapse ? 'icon-minus' : 'icon-plus'} />
      </TouchableOpacity>
      {showChild && (
        <Children
          list={item[childElement]}
          renderItem={itemChild => {
            return (
              <Pressable
                onPress={() => addToCart(itemChild)}
                key={itemChild.id}
                style={[
                  css.child,
                  selected.includes(itemChild.id)
                    ? { backgroundColor: PINK_OPACITY }
                    : {},
                ]}>
                <View
                  style={[
                    css.checkbox,
                    selected.includes(itemChild.id) ? css.checkBox : {},
                  ]}>
                  {selected.includes(itemChild.id) && (
                    <CustomIcon
                      icon={'icon-check'}
                      style={{ color: WHITE }}
                      size={12}
                    />
                  )}
                </View>
                <View style={{ flex: 1 }}>
                  <Text style={css.name}>{itemChild.name}</Text>
                  <View style={css.timeAndPrice}>
                    <Text style={css.time}>{itemChild.time} min</Text>
                    <Text style={css.price}>
                      {itemChild.price} {t('AZN')}
                    </Text>
                  </View>
                </View>
              </Pressable>
            );
          }}
        />
      )}
    </View>
  );
});

export default OneAccordion;

Его вызывает компонент

import React, { memo } from 'react';
import { ScrollView } from 'react-native';
import OneAccordion from './OneAccordion';

const Accordion = ({
  items = [],
  listStyle,
  masterId,
  childElement = 'children',
}) => {
  return (
    <ScrollView style={[listStyle]} showsVerticalScrollIndicator={false}>
      {items.map((item, index) => (
        <OneAccordion
          key={index}
          item={item}
          index={index}
          masterId={masterId}
          childElement={childElement}
        />
      ))}
    </ScrollView>
  );
};

export default memo(Accordion);

Который в свою очередь вызывается в компоненте

import React, { memo, useEffect, useState } from 'react';
import { ActivityIndicator, StyleSheet, Text, View } from 'react-native';
import Search from '../../../components/search/Search';
import Accordion from '../../../components/accordion/Accordion';
import { useQuery } from '@apollo/client';
import { SERVICES } from '../../../graphql/Query/Master';

const MasterServices = memo(
  ({ id }) => {
    const { loading, error, data } = useQuery(SERVICES, {
      variables: {
        id,
      },
    });
    const [list, setList] = useState(null);

    useEffect(() => {
      const arr = [];
      if (data !== undefined) {
        data.masterService.map(item => {
          arr.push({
            id: item.id,
            title: item.name,
            children: item.services,
            collapse: false,
          });
        });
        setList(arr);
      }
    }, [data]);

    useEffect(() => {
      console.log('[Update component]');
    }, []);

    return (
      <View style={css.content}>
        <Search />
        {error && <Text>{error.message}</Text>}
        {loading && <ActivityIndicator />}
        {list && (
          <Accordion
            items={list}
            masterId={id}
            listStyle={{ marginBottom: 50 }}
          />
        )}
      </View>
    );
  },
  (prevProps, nextProps) => {
    return prevProps.id !== nextProps.id;
  },
);

export default MasterServices;

Который вызывается на экране

import React, { useEffect, useLayoutEffect, useState } from 'react';
import {
  Image,
  SafeAreaView,
  Text,
  useWindowDimensions,
  View,
} from 'react-native';
import { css } from '../../styles/global';
import { useTranslation } from 'react-i18next';
import { styles } from '../../styles/Services.styles';
import BackButton from '../../components/backButton/BackButton';
import Load from '../../components/loadOrError/Load';
import { CustomIcon } from '../../components/customIcon/CustomIcon';
import { LIGHT_GRAY, PINK } from '../../styles/colors';
import { SceneMap, TabBar, TabView } from 'react-native-tab-view';
import MasterServices from './master/Services';
import { useDispatch, useSelector } from 'react-redux';
import { toggleTab } from '../../store/slices/systemSlice';
import Button from '../../components/button/Button';
import MasterAbout from './master/About';
import Courses from './master/Courses';
import MasterPortfolio from './master/Portfolio';
import MasterReview from './master/Review';
import { setCart, setTotal } from '../../store/slices/orderSlice';
import { useQuery } from '@apollo/client';
import { SERVICES_MASTER } from '../../graphql/Query/Master';
import Error from '../../components/loadOrError/Error';

const ServicesMaster = ({ route, navigation }) => {
  const { t } = useTranslation();
  const user = useSelector(state => state.user.user);
  const [routes] = useState([
    { key: 'services', title: t('Services') },
    { key: 'about', title: t('About master') },
    { key: 'courses', title: t('Courses') },
    { key: 'portfolio', title: t('Portfolio') },
    { key: 'review', title: t('Review') },
  ]);
  const [index, setIndex] = useState(0);
  const layout = useWindowDimensions();
  const dispatch = useDispatch();
  const serviceCart = useSelector(state => state.order.serviceCart);
  const [master, setMaster] = useState(null);
  const total = useSelector(state => state.order.total);
  const { data, loading, error, refetch } = useQuery(SERVICES_MASTER, {
    variables: {
      id: route.params.id,
      user_id: user.id,
    },
  });

  const renderScene = SceneMap({
    services: () => <MasterServices id={route.params.id} />,
    about: () => <MasterAbout id={route.params.id} />,
    courses: () => (
      <Courses
        id={route.params.id}
        onPress={id =>
          navigation.navigate('Academy', {
            screen: 'Academy.Course',
            params: {
              id,
            },
          })
        }
      />
    ),
    portfolio: () => <MasterPortfolio id={route.params.id} />,
    review: () => (
      <MasterReview
        id={route.params.id}
        onLeave={() =>
          navigation.navigate('Services.Master.LeaveReview', {
            id: route.params.id,
          })
        }
      />
    ),
  });

  const renderTabBar = props => (
    <TabBar
      {...props}
      indicatorStyle={{ backgroundColor: PINK, marginBottom: -2 }}
      scrollEnabled={true}
      style={{
        backgroundColor: 'transparent',
        borderBottomColor: LIGHT_GRAY,
        borderBottomWidth: 2,
        marginBottom: 40,
      }}
      renderLabel={({ route, focused, color }) => (
        <Text style={styles.labelTab}>{route.title}</Text>
      )}
    />
  );

  navigation.addListener('focus', () => {
    dispatch(toggleTab(false));
  });

  useLayoutEffect(() => {
    navigation.setOptions({
      title: '',
      headerLeft: () => <BackButton onPress={() => navigation.goBack()} />,
    });
  }, [navigation, t]);

  useEffect(() => {
    if (data) {
      setMaster(data.master);
      dispatch(setCart(data.serviceCart));
      let t = 0;
      data.serviceCart.map(e => {
        t += Number(e.price);
      });
      dispatch(setTotal(t));
    }
  }, [data]);

  if (loading || master === null) {
    return <Load />;
  }

  if (error) {
    console.log('[Log]', error.message);
    return <Error refetch={() => refetch()} />;
  }

  return (
    <SafeAreaView style={[css.content]}>
      <View style={styles.screen}>
        <BackButton
          onPress={() => navigation.goBack()}
          styles={{ position: 'absolute', zIndex: 2, evaluate: 2 }}
        />
        <View style={{ alignItems: 'center' }}>
          <Image
            style={styles.oneMasterImage}
            source={{ uri: master.main_image }}
          />
          <View style={styles.info}>
            <View style={styles.infoStar}>
              <CustomIcon
                icon={'icon-star-fill'}
                size={14}
                style={{ color: PINK, marginRight: 3 }}
              />
              <Text style={styles.rating}>{master.raiting}</Text>
            </View>
            <Text style={styles.review}>
              {master.count_review} {t('Reviews')}
            </Text>
          </View>
          <Text style={[styles.masterName, { fontSize: 22 }]}>
            {master.name} {master.first_name}
          </Text>
        </View>
      </View>
      <TabView
        lazy
        renderTabBar={renderTabBar}
        navigationState={{ index, routes }}
        renderScene={renderScene}
        onIndexChange={setIndex}
        initialLayout={{ width: layout.width }}
      />
      {serviceCart && serviceCart.length > 0 && (
        <View style={styles.cart}>
          <View
            style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
            <View
              style={{
                flexDirection: 'row',
                justifyContent: 'space-between',
                marginBottom: 10,
              }}>
              <Text style={styles.cartText}>
                {t('Selected')} ({serviceCart.length})
              </Text>
              {/*<Text style={styles.cartAction}>{t('Edit')}</Text>*/}
            </View>
            <Text style={styles.cartText}>
              {t('Total')}: {total} {t('AZN')}
            </Text>
          </View>
          <Button
            label={'Book Now'}
            type={'pink'}
            onPress={() =>
              navigation.navigate('ServicesMaster.Cart', {
                masterId: master.id,
              })
            }
          />
        </View>
      )}
    </SafeAreaView>
  );
};

export default ServicesMaster;

Когда в OneAccordion я отмечаю услугу, почему то происходит ререндерг всех компонентов, начиная с MasterServices. Не могу понять, почему? Уже все перепробовал

Ответы

Ответов пока нет.