realm-js: Cannot remove a result object's listener

Goals

I’m trying to update my UI based on changes in to a realm object, so I added a listener to the result that calls forceUpdate. This solution, while a little ugly since I don’t like calling forceUpdate, works correctly, but when I call removeListener on the collection, it still continues to call forceUpdate. I’ve also tried calling removeListener on the entire realm, as well as removeAllListeners on the result object and entire realm with no success.

I know its a no - op so it doesn’t affect the app, but it is a nuisance and I feel like there should be a correct way I haven’t found

Expected Results

the listener is removed correctly

Actual Results

it isn’t and a No-Op react native warning is thrown due to react trying to update an unmounted component

Steps to Reproduce

add a listener to a result object in componentWillMount and remove it in componentWillUnmount

Code Sample

In the file where i define my realm instance I have the following:

const listeners = [];

export function addFineGrainListener(objectName, filter, callback) {
  const collection = realm.objects(objectName).filtered(filter);
  collection.addListener(callback);
  listeners.push({ objectName, filter, collection, callback });
}

export function removeFineGrainListener(objectName, filter) {
  const listenerObject = listeners.find(
    listener => listener.objectName === objectName && listener.filter === filter,
  );

  listenerObject.collection.removeListener(listenerObject.callback);
}

and my component has the following:

  componentWillMount() {
    addFineGrainListener(
      'JournalEntry',
      \`id ==[c] "${this.props.screenProps.noteId}"\`,
      this.updateUIDueToRealmChange,
    );
  }
  componentWillUnmount() {
    removeFineGrainListener(
      'JournalEntry',
      \`id ==[c] "${this.props.screenProps.noteId}"\`,
    );
  }

  updateUIDueToRealmChange = () => {
    this.forceUpdate();
  };

Version of Realm and Tooling

  • Realm JS SDK Version: 2.1.1
  • Node or React Native: RN 51.0
  • Client OS & Version: IOS 11.2
  • Which debugger for React Native: Default Chrome Debugger

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 3
  • Comments: 32 (8 by maintainers)

Most upvoted comments

removeAllListeners does nothing as well on collections.

@gunnigylfa Strange, the same happen to me, but it was my fault. This code of yours should work: dogs.removeAllLIsteners() can you do a test for me? Put the dog variavel initializeing globally like this:


var dogListnerGlobal;
export default class BlaBlaBlaComponent extends React.Component {
  constructor(props) {
    super(props);
    dogListnerGlobal = realm.objects('Dog');
    dogListnerGlobal.addListener(this.watchDogsCallBack);
  }

  watchDogsCallBack = () => {
    alert("something has change");
  }

  componentWillUnmount = () => {
    dogListnerGlobal.removeAllListeners();
  }

}

Updated for the 2.6.0 documentation

@kneth Maybe like this?

this.collectionListenerRetainer = realm.objects('Dog').filtered('age < 2');

// Observe Collection Notifications
this.collectionListenerRetainer.addListener((puppies, changes) => {

  // Update UI in response to inserted objects
  changes.insertions.forEach((index) => {
    let insertedDog = puppies[index];
    ...
  });

  // Update UI in response to modified objects
  changes.modifications.forEach((index) => {
    let modifiedDog = puppies[index];
    ...
  });

  // Update UI in response to deleted objects
  changes.deletions.forEach((index) => {
    // Deleted objects cannot be accessed directly
    // Support for accessing deleted objects coming soon...
    ...
  });

});

// Unregister all listeners
realm.removeAllListeners();

// OR Unregister this listener
this.collectionListenerRetainer.removeAllListeners();

I think my problem has something to do with the way I was updating the component state in the function I assigned to the listener. I was fetching a new version of the objects and calling setState with those newly fetched items rather than using those passed to me via the callback. I’m not sure why that would be the case. I’m working on refactoring my code in order to make the new approach work for me. @FlaviooLima Thank you but I’m not sure if I can use this approach since I’m writing this in a type safe language which compiles to JS.

I will post an update of the problem here once I’m able to replicate with the new approach.

@andrew-wagner89 If you’re using redux you can do this hack:

wines.addListener((collection, changes) => {
  const { insertions, deletions, modifications } = changes;
  if (
    getState().wines.tracking &&
    (insertions.length || deletions.length || modifications.length)
  ) {
    dispatch(wineUpdate());
  }
});

Where wines is a reducer and tracking is a property you toggle on it. At least then even if it doesn’t get removed, you can intercept its non mounted call.