Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Native: using redux saga eventChannel to externalise event listeners from components

Redux saga eventChannel is a very elegant solution to connect to external events from outside your components code, keeping them cleaner and conciser. An use case could be to react to global App state changes, like application in the foreground, background, inactive...

I am worried about the unsubscribe process when it comes to React Native. Inside components, componentWillUnmount is the place to perform your un-subscriptions, so we have some UI hook that guarantees us the success of the listener removal.

When I apply the below code for instance, to keep track of App state changes (active, inactive, or background), could I be exposed to memory leaks?

type NextAppState = 'active' | 'inactive' | 'background';

function createAppStateChannel() {
  return eventChannel((emit: (nextState: NextAppState) => mixed) => {
    AppState.addEventListener('change', emit);
    return () => {
      AppState.removeEventListener('change', emit);
    };
  });
}

export function* appStateListenerSaga(): Generator<*, *, *> {
  try {
    const appStateChannel = yield call(createAppStateChannel);
    while (true) {
      const nextAppState: NextAppState = yield take(appStateChannel);
      if (nextAppState === 'active') {
        // Do something, like syncing with server
      }
  } finally {
    if (yield cancelled()) {
      appStateChannel.close();
    }
  } 
}

I am thinking of the case when the JS context gets killed for whatever reason.

My concern is that sagas wouldn't be cancelled (as far as I know), the un-subscription wouldn't be performed, so the native listener would remain registered. Next time the app would be reopened, we'd register a duplicated event listener and so on so forth.

Anyone could point me out whether this is safe or is it basically better to use a HOC? I am also using react-native-navigation where there are several react root views (one per screen), that's why HOC are not a good fit for that case, since I'd have to wrap every parent screen with the HOC and also if I have 3 screens pushed onto the stack, on app resume the logic would be executed 3 times.

like image 925
rgommezz Avatar asked Oct 29 '22 20:10

rgommezz


1 Answers

So apparently it depends on the implementation of the native module.

If the native module just keeps one listener and all the subscriptions are managed in JS side, then it’s not an issue.

However, if new listeners are handled on native side, it’ll be an issue if the JS context gets killed since memory would be leaked.

An example of two modules that keep subscriptions on the JS side are AppState and BackHandler. Both keep one global listener on the native side and multiple subscribers on the JS side, so both can be safely used with a redux-saga eventChannel.

You can check the source code of AppState in here as an example: https://github.com/facebook/react-native/blob/master/Libraries/AppState/AppState.js

I still haven't stumbled upon a module that does the other way around. As a rule of thumb, just glance at the module on the github source code to see how subscriptions are managed.

like image 92
rgommezz Avatar answered Nov 15 '22 06:11

rgommezz