setState causing memory leak inside subject subscribe

Viewed 146

I am facing error while navigating to TestPage second time. The code is working fine for first navigation, but I am getting error on second navigation.

Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

I have notices that the error does not occur if I remove the setinterval block.

The data from observable is reflecting fine in Text, but I am not able to understand what is causing this error. Hence my question here is why the setState is not working inside observable subscribe?

export const testSubject = new Subject<any>();
setInterval(() => {
    testSubject.next({ dest: 'any', msg: [] });
}, 2000);

function TestPage({ route, navigation }) {
    const [data, setData] = useState<any[]>([]);

    useFocusEffect(
        useCallback(() => {
            testSubject.subscribe(({ dest, msg }) => {
                setData(msg);
            });
            return () => _cleanup();
        }, []),
    );

    const _cleanup = () => {};

    return (
        <View>
            <Text>{JSON.stringify(data)}</Text>
            <Button
                onPress={() => {
                    navigation.navigate('home');
                }}
                title="back"
            />
        </View>
    );
}

export default TestPage;

1 Answers

I'm not sure what exactly is useFocusEffect(useCallback(...)), but I will assume it's the equivalent of a regular useEffect.

The problem you have is that you're not cleaning up the subscription to the stream when the component unmounts. When there's a new update on testSubject, the old instance (which unmounted) will call setData() on an unmount component, and react will complain.

Why does that _cleanup function does nothing? You just need to complete it:

    useFocusEffect(
        useCallback(() => {
            const subscription = testSubject.subscribe(({ dest, msg }) => {
                setData(msg);
            });
            return () => {
                subscription.unsubscribe();
            }
        }, []),
    );

This should work.

Related