admin管理员组文章数量:1319481
I am learning react native by myself and trying to develop an application.
What I understand is that whenever a component is re-rendered, all its children are re-rendered.
To simplify, Here are a few of my components in simplified way :
BorderedButton :
export default function BorderedButton({ title, onPress, textStyle, style, inProgress }) {
return (
<View style={[styles.container, style]}>
<Pressable
onPress={onPress}
disabled={inProgress}>
{!inProgress && <AppText style={[styles.text, textStyle]}>{title}</AppText>}
{
inProgress && <ActivityIndicator
animating={true}
color='white'
/>
}
</Pressable>
</View >
);
}
Feed Screen :
export default function FeedScreen() {
var accessToken = useSelector(state => state.authState.accessToken);
const isLoggedIn = useSelector(state => state.authState.isLoggedIn);
const userlatitude = useSelector(state => state.locationState.latitude);
const userLongitude = useSelector(state => state.locationState.longitude);
const isFocused = useIsFocused();
const navigation = useNavigation();
const [latitude, setLatitude] = useState(userlatitude);
const [longitude, setLongitude] = useState(userLongitude);
const [isHardRefreshPending, setIsHardRefreshPending] = useState(false);
console.log("DEBUGGGGG loading Feedscreen !");
if (!isLoggedIn) {
accessToken = undefined;
}
const intervalRef = useRef(null);
const [activities, setActivities] = useState([]);
const [loading, setLoading] = useState(false);
const [getData] = useLazyQuery(load_feed);
useEffect(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
continuationTokenRef.current = null;
setActivities([]);
refreshFeed(true);
}, [isLoggedIn, latitude, longitude]);
useEffect(() => {
if (activities === undefined
|| activities.length === 0) {
if (!intervalRef.current && !loading) {
const intervalId = setInterval(refreshFeed.bind(this, true), 5000);
intervalRef.current = intervalId;
}
} else {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}
}, [activities]);
useEffect(() => {
if (isHardRefreshPending) {
setIsHardRefreshPending(false);
refreshFeed(true);
}
}, [loading]);
function parseAndUpdateData(data, isHardRefresh) {
//logic to load/parse data and set activities state
//......
//... setActivities
}
function refreshFeed(isHardRefresh) {
setLoading(true);
getData({
context: {
headers: {
"Authorization": isLoggedIn ? `Bearer ${accessToken}` : ""
}
},
variables: {
latitude,
longitude,
size: defaultFeedSizeToFetch,
continuationToken: isHardRefresh ? null : continuationTokenRef.current
},
onCompleted: (data) => {
setLoading(false);
parseAndUpdateData(data, isHardRefresh);
},
onError: ({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map((message) => {
const errorJson = JSON.stringify(message);
isFocused && Alert.alert('Error', `Some issue getting the feed, please try again later`, [
{
text: 'Ok',
style: 'default'
}],
{
cancelable: false,
userInterfaceStyle: 'light'
}
);
})
}
if (networkError) {
isFocused && alert('please check your connection : ' + networkError.message);
}
setLoading(false);
}
})
}
function renderFeedCard(itemData) {
return (
<FeedCard activityData={itemData.item} />
);
}
const generateCustomUUID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
function getActivitiesOrPlaceHolderMessage() {
if (loading
&& (activities === undefined || activities.length === 0))
return (
<AppText>getting your feed ...</AppText>
);
if (activities && activities.length > 0)
return (
<View style={styles.activityContainer}>
<FlatList
keyExtractor={item => generateCustomUUID()}
data={activities}
renderItem={renderFeedCard}
contentContainerStyle={{
paddingBottom: 20
}}
onEndReached={refreshFeed.bind(this, false)}
style={{ zIndex: -1, flex: 1 }}
refreshing={true}
refreshControl={
<RefreshControl
refreshing={loading}
onRefresh={refreshFeed.bind(this, true)}
title="getting latest feed for you "
tintColor="#fff"
titleColor="#fff"
/>
}
/>
</View>
)
else
return (
<View>
<AppText>Oops ! seems like no activities happening around you. But you can try searching for activities around some other location by searching the location </AppText>
<BorderedButton
style={{
marginVertical: 20
}}
onPress={() => {
navigation.navigate("AddActivity");
}}
title={"Plan something now !"}
textStyle={{
fontFamily: "InterRegular"
}} />
</View>
);
}
return (
<PageContainer>
<View style={[styles.rootContainer, { display: backdropDisplay }]}>
<View style={styles.textContainer}>
<AppText style={styles.headingText}>Here are some amazing plans happening around you.</AppText>
</View>
<View style={styles.textContainer}>
<AppText style={styles.subHeadingText}>exciting new plans for you !</AppText>
</View>
{getActivitiesOrPlaceHolderMessage()}
</View>
</PageContainer>
);
}
FeedCard :
export default function FeedCard({ activityData }) {
console.log("Rendering Feed card for activity : ", activityData);
const isLoggedIn = useSelector(state => state.authState.isLoggedIn);
const [activity, setActivity] = useState({ ...activityData });
const [isApiInProgress, setIsApiInProgress] = useState(false);
const accessToken = useSelector(state => state.authState.accessToken);
const [triggerJoinActivityMutation] = useMutation(request_activity_joining, {
context: {
headers: {
"Authorization": `Bearer ${accessToken}`
}
},
variables: {
activityId: activity.activityId
},
onError: ({ graphQLErrors, networkError }) => {
console.log("DEBUGGGGG ERROR BLOCK");
setIsApiInProgress(false);
if (graphQLErrors) {
graphQLErrors.map((message, location, path) => {
const errorJson = JSON.stringify(message);
const errorObj = JSON.parse(errorJson);
Alert.alert('Error', `${errorObj.message}`, [
{
text: 'Ok',
style: 'default'
}],
{
cancelable: false,
userInterfaceStyle: 'light'
}
);
})
}
if (networkError) {
alert('please check your connection');
}
},
onCompleted: (response) => {
const status = response.requestActivityJoining.activityRequestStatus;
console.log("DEBUGGG status :", status);
setIsApiInProgress(false);
setActivity((prev) => {
const newActivity = { ...prev };
newActivity.requestStatus = status;
return newActivity;
})
}
});
const [triggerOptOutMutation] = useMutation(request_activity_opt_out, {
context: {
headers: {
"Authorization": `Bearer ${accessToken}`
}
},
variables: {
activityId: activity.activityId,
state: Math.random(),
},
onError: ({ graphQLErrors, networkError }) => {
setIsApiInProgress(false);
console.log("DEBUGGGG ERROR");
if (graphQLErrors) {
graphQLErrors.map((message, location, path) => {
const errorJson = JSON.stringify(message);
const errorObj = JSON.parse(errorJson);
Alert.alert('Error', `${errorObj.message}`, [
{
text: 'Ok',
style: 'default'
}],
{
cancelable: false,
userInterfaceStyle: 'light'
}
);
})
}
if (networkError) {
alert('please check your connection');
}
},
onCompleted: (response) => {
const status = response.requestActivityOptOut.activityRequestStatus;
console.log('request status in opt out api response', status);
setIsApiInProgress(false);
setActivity((prev) => {
const newActivity = { ...prev };
newActivity.requestStatus = status;
return newActivity;
})
}
});
function initiateActivityJoiningRequest() {
console.log("trigger join mutation");
if (!isLoggedIn) {
Alert.alert("Please log in to participate in this activity ");
return;
}
setIsApiInProgress(true);
console.log("trigger join mutation ------ 2");
triggerJoinActivityMutation();
}
function initiateActivityOptOut() {
console.log("trigger opt out mutation");
setIsApiInProgress(true);
console.log("trigger opt out mutation ----- 2");
triggerOptOutMutation();
}
function getButtonComponent() {
const activityRequestStatus = activity.requestStatus;
if (activityRequestStatus && activityRequestStatus == 'PENDING_HOST_APPROVAL'
|| activityRequestStatus && activityRequestStatus == 'HOST_APPROVED') {
return (
<BorderedButton
onPress={initiateActivityOptOut}
inProgress={isApiInProgress}
title={'Opt out'}
/>
);
}
return (
<BorderedButton
title={'Request'}
onPress={initiateActivityJoiningRequest}
inProgress={isApiInProgress} />
);
}
return (
<View style={[styles.rootContainer]} >
<View style={styles.userAndCompanyInfoContainer}>
<UserInfo
relativeSize={userInfoRelativeSize}
userInfo={{
userId: activity.hostId,
displayName: activity.hostDisplayName,
profileImageUrl: activity.profileImageUrl
}}
/>
<CompanyInfo
relativeSize={userInfoRelativeSize}
Name={activity.hostOrgName} />
</View>
<View style={styles.titleContainer}>
<FeedCardTitle>{activity.title}</FeedCardTitle>
</View>
<View style={styles.descriptionContainer}>
<AppText style={styles.descriptionText}>{activity.description}</AppText>
</View>
<View style={styles.locationContainer}>
<LocationCard locationDescription={activity.locationDescription} />
</View>
<View style={styles.dateAndTimeContainer}>
<FeedCardDate dateDay={activity.dateDay} dateMonth={activity.dateMonth} />
<TimeInfo time={activity.tentativeTime} />
</View>
<View style={styles.buttonContainer}>
<ParticipantsSnapshot
participantsImages={[...activityData.participantsImages]} />
{getButtonComponent()}
</View>
</View >
);
}
The behaviour I am unable to relate with is - When a button in pressed in the FeedCard, it calls the method triggerJoinActivityMutation or triggerOptOutMutation. I am setting the state isApiInProgress to true, which is used by the button component to show the loading animation. But whenever setIsApiInProgress is called in any of these 2 methods, it also re-renders the FeedScreen. Ideally the state change only moves downwards (what I have learned) then why does the feed screen reloads ?
I know this because the moment I comment all setIsApiInPogress method calls, things start working the way as expected, the FeedScreen never re-renders
My understanding is as follows - Feedscreen renders a list of FeedCards -> which in turn uses BorderedButton. Bordered button uses a state(isApiInProgress) which belongs to FeedCard. So when the state(isApiInProgress is updated) it should re-render the FeedCard and BorderedButton and not the FeedScreen.
Can anyone help me understand this behaviour. I tried exploring the web but could not understand the root cause of this behaviour.
I am learning react native by myself and trying to develop an application.
What I understand is that whenever a component is re-rendered, all its children are re-rendered.
To simplify, Here are a few of my components in simplified way :
BorderedButton :
export default function BorderedButton({ title, onPress, textStyle, style, inProgress }) {
return (
<View style={[styles.container, style]}>
<Pressable
onPress={onPress}
disabled={inProgress}>
{!inProgress && <AppText style={[styles.text, textStyle]}>{title}</AppText>}
{
inProgress && <ActivityIndicator
animating={true}
color='white'
/>
}
</Pressable>
</View >
);
}
Feed Screen :
export default function FeedScreen() {
var accessToken = useSelector(state => state.authState.accessToken);
const isLoggedIn = useSelector(state => state.authState.isLoggedIn);
const userlatitude = useSelector(state => state.locationState.latitude);
const userLongitude = useSelector(state => state.locationState.longitude);
const isFocused = useIsFocused();
const navigation = useNavigation();
const [latitude, setLatitude] = useState(userlatitude);
const [longitude, setLongitude] = useState(userLongitude);
const [isHardRefreshPending, setIsHardRefreshPending] = useState(false);
console.log("DEBUGGGGG loading Feedscreen !");
if (!isLoggedIn) {
accessToken = undefined;
}
const intervalRef = useRef(null);
const [activities, setActivities] = useState([]);
const [loading, setLoading] = useState(false);
const [getData] = useLazyQuery(load_feed);
useEffect(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
continuationTokenRef.current = null;
setActivities([]);
refreshFeed(true);
}, [isLoggedIn, latitude, longitude]);
useEffect(() => {
if (activities === undefined
|| activities.length === 0) {
if (!intervalRef.current && !loading) {
const intervalId = setInterval(refreshFeed.bind(this, true), 5000);
intervalRef.current = intervalId;
}
} else {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}
}, [activities]);
useEffect(() => {
if (isHardRefreshPending) {
setIsHardRefreshPending(false);
refreshFeed(true);
}
}, [loading]);
function parseAndUpdateData(data, isHardRefresh) {
//logic to load/parse data and set activities state
//......
//... setActivities
}
function refreshFeed(isHardRefresh) {
setLoading(true);
getData({
context: {
headers: {
"Authorization": isLoggedIn ? `Bearer ${accessToken}` : ""
}
},
variables: {
latitude,
longitude,
size: defaultFeedSizeToFetch,
continuationToken: isHardRefresh ? null : continuationTokenRef.current
},
onCompleted: (data) => {
setLoading(false);
parseAndUpdateData(data, isHardRefresh);
},
onError: ({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
graphQLErrors.map((message) => {
const errorJson = JSON.stringify(message);
isFocused && Alert.alert('Error', `Some issue getting the feed, please try again later`, [
{
text: 'Ok',
style: 'default'
}],
{
cancelable: false,
userInterfaceStyle: 'light'
}
);
})
}
if (networkError) {
isFocused && alert('please check your connection : ' + networkError.message);
}
setLoading(false);
}
})
}
function renderFeedCard(itemData) {
return (
<FeedCard activityData={itemData.item} />
);
}
const generateCustomUUID = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
function getActivitiesOrPlaceHolderMessage() {
if (loading
&& (activities === undefined || activities.length === 0))
return (
<AppText>getting your feed ...</AppText>
);
if (activities && activities.length > 0)
return (
<View style={styles.activityContainer}>
<FlatList
keyExtractor={item => generateCustomUUID()}
data={activities}
renderItem={renderFeedCard}
contentContainerStyle={{
paddingBottom: 20
}}
onEndReached={refreshFeed.bind(this, false)}
style={{ zIndex: -1, flex: 1 }}
refreshing={true}
refreshControl={
<RefreshControl
refreshing={loading}
onRefresh={refreshFeed.bind(this, true)}
title="getting latest feed for you "
tintColor="#fff"
titleColor="#fff"
/>
}
/>
</View>
)
else
return (
<View>
<AppText>Oops ! seems like no activities happening around you. But you can try searching for activities around some other location by searching the location </AppText>
<BorderedButton
style={{
marginVertical: 20
}}
onPress={() => {
navigation.navigate("AddActivity");
}}
title={"Plan something now !"}
textStyle={{
fontFamily: "InterRegular"
}} />
</View>
);
}
return (
<PageContainer>
<View style={[styles.rootContainer, { display: backdropDisplay }]}>
<View style={styles.textContainer}>
<AppText style={styles.headingText}>Here are some amazing plans happening around you.</AppText>
</View>
<View style={styles.textContainer}>
<AppText style={styles.subHeadingText}>exciting new plans for you !</AppText>
</View>
{getActivitiesOrPlaceHolderMessage()}
</View>
</PageContainer>
);
}
FeedCard :
export default function FeedCard({ activityData }) {
console.log("Rendering Feed card for activity : ", activityData);
const isLoggedIn = useSelector(state => state.authState.isLoggedIn);
const [activity, setActivity] = useState({ ...activityData });
const [isApiInProgress, setIsApiInProgress] = useState(false);
const accessToken = useSelector(state => state.authState.accessToken);
const [triggerJoinActivityMutation] = useMutation(request_activity_joining, {
context: {
headers: {
"Authorization": `Bearer ${accessToken}`
}
},
variables: {
activityId: activity.activityId
},
onError: ({ graphQLErrors, networkError }) => {
console.log("DEBUGGGGG ERROR BLOCK");
setIsApiInProgress(false);
if (graphQLErrors) {
graphQLErrors.map((message, location, path) => {
const errorJson = JSON.stringify(message);
const errorObj = JSON.parse(errorJson);
Alert.alert('Error', `${errorObj.message}`, [
{
text: 'Ok',
style: 'default'
}],
{
cancelable: false,
userInterfaceStyle: 'light'
}
);
})
}
if (networkError) {
alert('please check your connection');
}
},
onCompleted: (response) => {
const status = response.requestActivityJoining.activityRequestStatus;
console.log("DEBUGGG status :", status);
setIsApiInProgress(false);
setActivity((prev) => {
const newActivity = { ...prev };
newActivity.requestStatus = status;
return newActivity;
})
}
});
const [triggerOptOutMutation] = useMutation(request_activity_opt_out, {
context: {
headers: {
"Authorization": `Bearer ${accessToken}`
}
},
variables: {
activityId: activity.activityId,
state: Math.random(),
},
onError: ({ graphQLErrors, networkError }) => {
setIsApiInProgress(false);
console.log("DEBUGGGG ERROR");
if (graphQLErrors) {
graphQLErrors.map((message, location, path) => {
const errorJson = JSON.stringify(message);
const errorObj = JSON.parse(errorJson);
Alert.alert('Error', `${errorObj.message}`, [
{
text: 'Ok',
style: 'default'
}],
{
cancelable: false,
userInterfaceStyle: 'light'
}
);
})
}
if (networkError) {
alert('please check your connection');
}
},
onCompleted: (response) => {
const status = response.requestActivityOptOut.activityRequestStatus;
console.log('request status in opt out api response', status);
setIsApiInProgress(false);
setActivity((prev) => {
const newActivity = { ...prev };
newActivity.requestStatus = status;
return newActivity;
})
}
});
function initiateActivityJoiningRequest() {
console.log("trigger join mutation");
if (!isLoggedIn) {
Alert.alert("Please log in to participate in this activity ");
return;
}
setIsApiInProgress(true);
console.log("trigger join mutation ------ 2");
triggerJoinActivityMutation();
}
function initiateActivityOptOut() {
console.log("trigger opt out mutation");
setIsApiInProgress(true);
console.log("trigger opt out mutation ----- 2");
triggerOptOutMutation();
}
function getButtonComponent() {
const activityRequestStatus = activity.requestStatus;
if (activityRequestStatus && activityRequestStatus == 'PENDING_HOST_APPROVAL'
|| activityRequestStatus && activityRequestStatus == 'HOST_APPROVED') {
return (
<BorderedButton
onPress={initiateActivityOptOut}
inProgress={isApiInProgress}
title={'Opt out'}
/>
);
}
return (
<BorderedButton
title={'Request'}
onPress={initiateActivityJoiningRequest}
inProgress={isApiInProgress} />
);
}
return (
<View style={[styles.rootContainer]} >
<View style={styles.userAndCompanyInfoContainer}>
<UserInfo
relativeSize={userInfoRelativeSize}
userInfo={{
userId: activity.hostId,
displayName: activity.hostDisplayName,
profileImageUrl: activity.profileImageUrl
}}
/>
<CompanyInfo
relativeSize={userInfoRelativeSize}
Name={activity.hostOrgName} />
</View>
<View style={styles.titleContainer}>
<FeedCardTitle>{activity.title}</FeedCardTitle>
</View>
<View style={styles.descriptionContainer}>
<AppText style={styles.descriptionText}>{activity.description}</AppText>
</View>
<View style={styles.locationContainer}>
<LocationCard locationDescription={activity.locationDescription} />
</View>
<View style={styles.dateAndTimeContainer}>
<FeedCardDate dateDay={activity.dateDay} dateMonth={activity.dateMonth} />
<TimeInfo time={activity.tentativeTime} />
</View>
<View style={styles.buttonContainer}>
<ParticipantsSnapshot
participantsImages={[...activityData.participantsImages]} />
{getButtonComponent()}
</View>
</View >
);
}
The behaviour I am unable to relate with is - When a button in pressed in the FeedCard, it calls the method triggerJoinActivityMutation or triggerOptOutMutation. I am setting the state isApiInProgress to true, which is used by the button component to show the loading animation. But whenever setIsApiInProgress is called in any of these 2 methods, it also re-renders the FeedScreen. Ideally the state change only moves downwards (what I have learned) then why does the feed screen reloads ?
I know this because the moment I comment all setIsApiInPogress method calls, things start working the way as expected, the FeedScreen never re-renders
My understanding is as follows - Feedscreen renders a list of FeedCards -> which in turn uses BorderedButton. Bordered button uses a state(isApiInProgress) which belongs to FeedCard. So when the state(isApiInProgress is updated) it should re-render the FeedCard and BorderedButton and not the FeedScreen.
Can anyone help me understand this behaviour. I tried exploring the web but could not understand the root cause of this behaviour.
Share Improve this question asked Jan 19 at 21:10 amit guptaamit gupta 611 silver badge4 bronze badges1 Answer
Reset to default 0This is not particular to React Native; it is just normal React behavior.
If state changes in a component, React will always re-render it. Even if the state isn't being "used" by the component (i.e., just passed down to a child).
As a trivial example, the following component re-renders every time the button is pressed, even though "state" isn't actually being used anywhere:
const Component = () => {
const [state, setState] = useState({});
return <button onClick={() => setState({})}>Press me</button>;
};
So calling setIsApiInProgress
is expected to re-render the screen. It shouldn't cause significant performance issues as long as the rest of your screen is designed well.
本文标签: react nativeWhy is child component rerendering parent componentStack Overflow
版权声明:本文标题:react native - Why is child component re-rendering parent component - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1742058895a2418463.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论