React Native: запретить ре-рендеринг дочернего компонента
У меня есть такой компонент
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
. Не могу понять, почему? Уже все перепробовал
Источник: Stack Overflow на русском