Showing refresh icon on the top of the screen while navigating back - React native

1.1k Views Asked by At

Showing a white refresh circle on the top while navigating back from a screen having refresh control in the flatlist. This issue is occuring in the android devices only and not occuring in the android OS 8.

this issue won’t come if i refresh the screen at-least once when mounted and issue won’t come if i comment the refresh control.

i'm attaching the sample code below , please help me to fix this issue.

import * as React from 'react';
import { View, Text, FlatList, StyleSheet, Button, RefreshControl } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';

function HomeScreen(props) {
    return (
        <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
            <Button
                title="Go to Next Screen"
                onPress={() => props.navigation.navigate('FlatList')} />

        </View>
    );
}
const DATA = [
    {
        id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
        title: 'First Item',
    },
    {
        id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
        title: 'Second Item',
    },
    {
        id: '58694a0f-3da1-471f-bd96-145571e29d72',
        title: 'Third Item',
    },
];

const Item = ({ title }) => (
    <View style={styles.item}>
        <Text style={styles.title}>{title}</Text>
    </View>
);

const FlatListScreen = () => {
    const renderItem = ({ item }) => (
        <Item title={item.title} />
    );

    return (
        <View style={styles.container}>
            <FlatList
                data={DATA}
                style={{ marginTop: 50 }}
                renderItem={renderItem}
                keyExtractor={(item, index) => index.toString()}
                refreshControl={<RefreshControl
                    onRefresh={() => { }}
                    refreshing={false}
                />
                }
            />
        </View>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: 'black'
    },
    item: {
        backgroundColor: '#f9c2ff',
        padding: 20,
        marginVertical: 8,
        marginHorizontal: 16,
    },
    title: {
        fontSize: 32,
    },
});

const Stack = createNativeStackNavigator();

function App() {
    return (
        <NavigationContainer>
            <Stack.Navigator>
                <Stack.Screen name="Home" component={HomeScreen} />
                <Stack.Screen name="FlatList" component={FlatListScreen} />
            </Stack.Navigator>
        </NavigationContainer>
    );
}

export default App;
2

There are 2 best solutions below

0
Lucas Basquerotto On

It seems to be a bug in the RefreshControl. This happens when it's unmounted.

There's an open issue for that: https://github.com/facebook/react-native/issues/34718

Someone posted a workaround: https://github.com/facebook/react-native/issues/34718#issuecomment-1295919690

The issue itself must be solved upstream tough.

Below is the workaround (I changed a bit to allow to specify a custom offset, because I use it in my code):

const selectHook = <T,>({
    useDefault,
    useAndroid,
}: {
    useDefault: T;
    useAndroid: T;
}) => {
    return Platform.select({
        default: useDefault,
        android: useAndroid,
    });
};

/**
 * Quick workaround for refreshing issue on Android:
 * The refresh control is showing up above the header when unmounting the screen.
 * Note: It's happening when using a custom header component, but didn't I try using
 * native header from the native stack.
 * See: https://github.com/facebook/react-native/issues/34718
 */
const useProgressViewOffset = selectHook({
    useDefault: (offset?: number) => offset,
    useAndroid: (offset?: number) => {
        const navigation = useNavigation();
        const [progressViewOffset, setProgressViewOffset] = useState(offset);
        const goBackEventWasHandled = useRef(false);

        // prevent the navigation event and hide the refresh indicator
        React.useEffect(() => {
            const unsubscribe = navigation.addListener('beforeRemove', (event) => {
                // Handle GO_BACK event only, because it fits my use case, please tweak it to fit yours
                if (
                    event.data.action.type === 'GO_BACK' &&
                    !goBackEventWasHandled.current
                ) {
                    event.preventDefault();
                    goBackEventWasHandled.current = true;
                    setProgressViewOffset(-1000); // set to a ridiculous value to hide the refresh control
                }
            });

            return unsubscribe;
        }, [navigation]);

        // perform the navigation with the hidden refresh indicator
        React.useEffect(() => {
            if (progressViewOffset !== undefined && goBackEventWasHandled.current) {
                navigation.goBack();
            }
        }, [navigation, progressViewOffset]);

        return progressViewOffset;
    },
});

Then you can use it in your code like:

// ...
const progressViewOffset = useProgressViewOffset()
// ...
<RefreshControl
  {...refreshProps}
  progressViewOffset={progressViewOffset}
/>
0
Michael On

Still happens in react native v0.71.7

The answer presented from Lucas Basquerotto works for me.

If you having problems (with the linter, e.g. rome) after copying and pasting the answer from here https://github.com/facebook/react-native/issues/34718#issuecomment-1295919690, add a comma to the <T,> at const selectHook = <T,>

const selectHook = <T,>({ useIos, useAndroid }: { useIos: T; useAndroid: T }) => {
    return Platform.select({
        default: useIos,
        android: useAndroid,
    });
};

const useProgressViewOffset = selectHook({
    useAndroid: () => {
        const navigation = useNavigation();
        const [progressViewOffset, setProgressViewOffset] = useState<undefined | number>(undefined);
        const goBackEventWasHandled = useRef(false);

        // prevent the navigation event and hide the refresh indicator
        useEffect(() => {
            const unsubscribe = navigation.addListener("beforeRemove", (event) => {
                // Handle GO_BACK event only, because it fits my use case, please tweak it to fit yours
                if (event.data.action.type === "GO_BACK" && !goBackEventWasHandled.current) {
                    event.preventDefault();
                    goBackEventWasHandled.current = true;
                    setProgressViewOffset(-1000); // set to a ridiculous value to hide the refresh control
                }
            });

            return unsubscribe;
        }, [navigation]);

        // perform the navigation with the hidden refresh indicator
        useEffect(() => {
            if (progressViewOffset !== undefined) {
                navigation.goBack();
            }
        }, [navigation, progressViewOffset]);

        return progressViewOffset;
    },

    // No need to do anything on iOS
    useIos: () => undefined,
});
export default useProgressViewOffset;