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 badges
Add a comment  | 

1 Answer 1

Reset to default 0

This 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