realm / realm-js

Realm is a mobile database: an alternative to SQLite & key-value stores
https://realm.io
Apache License 2.0
5.62k stars 558 forks source link

Accessing object of type X which has been invalidated or deleted #1031

Closed L3V147H4N closed 3 years ago

L3V147H4N commented 6 years ago

Even though the .isValid method is present in both collection and object, neither of them is doing anything to prevent this error.

I tried using Collection.isValid, Object.isValid, Results.snapshot .. nothing works

I'm rendering the Results on one view and if I delete one Object in another view and then return it throws this error.

photo_2017-05-24_11-37-18

here is my code for the 2 views

import React, { Component } from 'react';
import {
  InteractionManager
} from 'react-native';
import {
  Body,
  Button,
  Container,
  Content,
  Fab,
  Footer,
  FooterTab,
  Header,
  Icon,
  Input,
  Item,
  Left,
  List,
  ListItem,
  Right,
  Text,
  Title,
  View
} from 'native-base';
import realm from '../realm';

class CharactersScreen extends Component {

  constructor (props) {
    super(props);

    this.characters = realm.objects('Character');
    this.state = {
      searchBar: {
        active: false
      },
      characters: this.characters
    };
  }

  navigateToAddCharacter = () => {
    let { navigate } = this.props.navigation;

    navigate('AddCharacter');
  }

  static navigationOptions = {
    title: 'Characters',
    headerRight: <View style={{ flexDirection: 'row' }}>
      <Button transparent><Icon name="md-search"/></Button>
      <Button transparent><Icon name="md-funnel"/></Button>
    </View>
  }

  listenToRealm = (name, changes) => {
    this.setState({
      characters: this.characters
    });
  }

  componentWillMount () {
    InteractionManager.runAfterInteractions(() => {
      this.characters.addListener(this.listenToRealm);
    });
  }

  componentWillUnmount () {
    this.characters.removeListener(this.listenToRealm);
  }

  render () {
    let { navigate } = this.props.navigation;
    let {
      searchBar,
      characters
    } = this.state;

    return (
      <Container>
        {
          searchBar.active ?
            <Header searchBar rounded>
              <Item>
                <Icon name="md-search" />
                <Input placeholder="Search" />
                <Icon name="md-people" />
              </Item>
              <Button transparent>
                <Text>Search</Text>
              </Button>
            </Header> : null
        }
        <Content>
          <List>
            {
              characters.map((character, index) => {
                return (
                  <ListItem
                    key={index} onPress={navigate.bind(null, 'CharacterDetails', {
                      character
                    })}>
                    <Body>
                      <Text>{character.name}</Text>
                      <Text note>{character.note}</Text>
                    </Body>
                    <Right>
                      <Icon name="md-arrow-round-forward" />
                    </Right>
                  </ListItem>
                );
              })
            }
          </List>
        </Content>
        <Fab
          active={false}
          direction="top"
          containerStyle={{ marginLeft: 10 }}
          style={{ backgroundColor: '#5067FF' }}
          position="bottomRight"
          onPress={this.navigateToAddCharacter}>
            <Icon name="md-add" />
        </Fab>
      </Container>
    );
  }
}

export default CharactersScreen;

Here is the view where I delete the object

import React, { Component } from 'react';
import {
  Alert
} from 'react-native';
import {
  Body,
  Button,
  Card,
  CardItem,
  Container,
  Content,
  Icon,
  Left,
  Right,
  Text
} from 'native-base';
import realm from '../realm';

class CharacterDetailsScreen extends Component {

  static navigationOptions = {
    title: 'Character: Details'
  }

  promptDelete = () => {
    Alert.alert(
      'Delete Character',
      'Deleting is irreversible, are you sure?',
      [
        { text: 'Cancel', onPress: () => false },
        { text: 'OK', onPress: () => this.deleteCharacter() }
      ],
      { cancelable: false }
    );
  }

  deleteCharacter = () => {
    let {
      state: {
        params: {
          character
        }
      }
    } = this.props.navigation;
    let { goBack } = this.props.navigation;

    realm.write(() => {
      realm.delete(character);
    });

    goBack();
  }

  render () {
    let {
      navigate,
      state: {
        params: {
          character
        }
      }
    } = this.props.navigation;

    return (
      <Container>
        <Content>
          <Card>
            <CardItem header>
              <Left>
                <Icon name={character.favorite ? 'md-star' : 'md-person'}/>
                <Body>
                  <Text>{character.name}</Text>
                  <Text note>{character.note}</Text>
                </Body>
              </Left>
            </CardItem>
            <CardItem>
              <Body>
                <Text>{character.description}</Text>
              </Body>
            </CardItem>
            <CardItem>
              <Body>
                <Text>Status</Text>
                <Text note>{character.status}</Text>
              </Body>
            </CardItem>
            <CardItem>
              <Left>
                <Button onPress={this.promptDelete} transparent>
                  <Icon name="md-close" style={{ color: '#FF0000' }} />
                  <Text style={{ color: '#FF0000' }}> Delete</Text>
                </Button>
              </Left>
              <Right>
                <Button transparent>
                  <Icon name="md-create" />
                  <Text> Edit</Text>
                </Button>
              </Right>
            </CardItem>
          </Card>
        </Content>
      </Container>
    );
  }
}

export default CharacterDetailsScreen;
EyalSi commented 6 years ago

I have the same Error!. It seems that if you pass an object to navigation, delete this object (while the object is still in the navigation stack), - it will trigger this error. I found it is working if I pass an id, retrieve again the object on the navigated view and then make the deletion. But this is sure not the right solution...

JamesLi2013 commented 6 years ago

I have the same Error too.situation : React Native Android ,Navigation ,delete the object,when click back,error happen.

gutoglup commented 6 years ago

I have the same situation in iOS.

lossen commented 6 years ago

I have the same error :(

irsyadilham commented 6 years ago

+1 same error

jgreen01su commented 6 years ago

You need to use Realm.open and you'll need to query for the object you want to delete.

Also it's a good idea to separate your realm code from your presentation code, see discussion here.

This is how I'm using realm to dispatch a redux action using redux-thunk.

const retriveSettings = () => (dispatch) => {
  return Realm.open({
    schema: [SettingsSchema],
    schemaVersion: SettingsSchema.schemaVersion
  })
  .then(realm => {
    const settings = realm.objects('Settings').find(({ id }) => (id === 0));
    dispatch({
      type: types.RETRIEVED_SETTINGS,
      settings
    });
  })
  .catch(e => console.log(e));
}
EyalSi commented 6 years ago

Any chance to check this one? Otherwise, it's a massive refactor...

lossen commented 6 years ago

I found what I was a mistake. After removal of the Realm Object, I have not updated the store and apply to him on the screen where I had this error. That is, the problem was that I apply to already remote data. I solved the problem, just updating the store after deleting the Realm Object. Perhaps someone it will be useful.

martijngerrits commented 6 years ago

I had this issue as wel. The reason why was that I was referencing to an object of the Realm DB in a list view. In my particular case the solution was creating a clone of the object to use in the list: Object.assign({}, realmObject);

This way when a row was deleted all references within the row pointed to that cloned object instead of an object from realm that did not exist anymore.

Maybe this is of some use for someone ;)

radiegtya commented 6 years ago

+1

lodev09 commented 6 years ago

in your schemas, extend them with Realm.Object. You can then call object.isValid() to check if the object still exists in realm.

see example here

fee-studio commented 6 years ago

isValid() also not work...

stlemon commented 6 years ago

how to resolve this problem? had the same error

prabhu commented 6 years ago

Wasted everyone's time in the last couple of days with this bug which was getting wrapped in another error in our codebase. Any fixes in sight?

minhcasi commented 6 years ago

same error !

inspireui commented 6 years ago

resolved my issues, just make sure the Realm object not loading from Redux store after deleted then it will be fixed.

vance-liu commented 6 years ago

same error ! I delete one then query all, red box~

vance-liu commented 6 years ago

resolved this error, just do not filter array of RealmObject, I convert each RealmObject to PlainObject, and it works well.

stlemon commented 6 years ago

@vance-liu 你好,convert each RealmObject to PlainObject 如何实现?可以贴一下代码吗?被这个问题困扰好久了~

huuquan95 commented 6 years ago

Thanks all, I make the copy of the Realm array object, so I did well. Maybe it has links between realm objects. Can you guys explain more for me? I have just do with ReactNative for 2 months.

esutton commented 6 years ago

@inspireui Can you please elaborate on how you resolved your issue?

resolved my issues, just make sure the Realm object not loading from Redux store after deleted then it will be fixed.

I have several detail views of the opened realm object loaded in scenes from react-native-router-flux.

When objected being viewed is deleted I can find no way to get these detail views to gracefully release the deleted the object to prevent a crash no matter how many try catch blocks I add to the detail view scene.

I feel like to avoid a crash I must mark it for deletion and then actually delete it when I know no views are referencing it.

EyalSi commented 6 years ago

We're almost celebrating this bug it's first birthday, and from the number of people involved here, it seems like it cost a lot of us hours of investigation, with no elegant solution. Is there any chance this one will be solved? 10x!!!

adrianolsk commented 6 years ago

@EyalSi More then a year (yay), so to solve the problem here I did this:

export const listTasks = (start=0, take=10) =>
  new Promise((resolve, reject) => {
    Realm.open(config)
      .then(realm => {
        let tasks = realm.objects(TASK_SCHEMA).slice(start, start+ take );
        resolve(tasks.map(task=>({
            ...task
        })));
      })
      .catch(error => reject(error));
  });

So doing tasks.map(task=>({ ...task }))); transform the realm objects in plain objects Now when I delete the object from the realm it wont give the Accessing object of type X which has been invalidated or deleted error.

Another way to avoid the error is using a listener in the filtered realm, so when you delete your objects will be updated. But I was having performances issues with huge lists so I had to paginate the records.

deboyblog commented 6 years ago
let arrayResults = Array.form(realmResults)
plainResults = JSON.parse(JSON.stringfy(results)) // array
EyalSi commented 6 years ago

Duplicating the object will result in performance degradation. Hopped to get a fix in realm.

donni106 commented 6 years ago

For me the array transformation does not work.

people() {
  const people = realm.objects('Person').filtered('name != $0', null);
  setTimeout(() => {
    realm.close();
  }, 100);
  return Array.from(people);
}
  1. without the setTimeout I get: Error: Access to invalidated Results objects, why? And what time could be chosen wisely?
  2. when returning correctly, I get the error: Accessing object of type Person which has been invalidated or deleted

What can I do in order to continue working?

erick2014 commented 6 years ago

I came up with a solution, if you are using react-navigation along with redux and realm, you could simply reset the state of react-navigation like this:

import { NavigationActions } from 'react-navigation'

const resetNavigationState = (navigation, routeName = 'Login') => {
  const resetAction = NavigationActions.reset({
    index: 0,
    actions: [
      NavigationActions.navigate({ routeName: routeName })
    ]

  })
  navigation.dispatch(resetAction)
}

routeName is the stack's navigator screen where you want to go, for example if I want to reset the state and go to HomePage screen, I do something like this:

resetNavigationState(this.props.navigation, 'HomePage')

theapache64 commented 5 years ago

In Node.js, I could "PATCH" the issue with below given code.

const genreJsonString = JSON.stringify(genre);

    genreRealm.write(()=>{
       genreRealm.delete(genre);
    });

    resp.send(JSON.parse(genreJsonString));
kunalqss commented 5 years ago

@lossen how do we reset the redux store ? I created a dispatch action (I'm using redux-saga) to "reset" the state to its initial state but still got this error. I followed this.

kenkotch commented 5 years ago

I've gotten this error a few times but cant get it to reliably repeat. Can anyone confirm this only happens in debug builds, not release? Obviously on release we wouldn't get the warning, just failure.

shllg commented 5 years ago

Same issue here. I'm writing an object to realm and it's visible via Realm Studio. So it's definitely persisted. Then when fetching the Objects of the Model, each value of each model ist "Accessing object..." like the error message. The objects were loaded once in development. Since then, it didn't work anymore. No code changes in between...

kunalqss commented 5 years ago

This is how I solved this problem -

First I created a function in my utils package -

export function convertToArray(realmObjectsArray)
{
    let copyOfJsonArray = Array.from(realmObjectsArray);
    let jsonArray = JSON.parse(JSON.stringify(copyOfJsonArray));
    return jsonArray;
}

Then, I used it to query Realm -

...
let allServicePlans = realm.objects(ScheduleSchema.name);
allServicePlans = convertToArray(allServicePlans);
return allServicePlans;
...

Haven't noticed any performance issues till now, and have also gotten rid of that error for now it seems.

adrianolsk commented 5 years ago

@kunalqss Could you try just this to see if it works for you?

let allServicePlans = realm.objects(ScheduleSchema.name);
return allServicePlans.map(service=>({ ...service })));;
CryptoZ258 commented 5 years ago

same error

esutton commented 5 years ago

@lodev09 Thank you for the example on how to extend my class

class Car extends Realm.Object {

I can now call isValid() on my object.

Unfortunately isValid() always returns true.

Still trying to find a solution to app crashes when referencing invalidated or deleted Realm objects.

lodev09 commented 5 years ago

@esutton it shouldn't return true if the object is not valid e.g. deleted from realm

mitchclevertronics commented 5 years ago

try

if realmobject.isInvalidated { }

EnettyTech commented 5 years ago

I still issues, how to resolve it?

diegolmello commented 5 years ago

Hi, there. IMO this is not a Realm issue, because it's just a mutable object being passed around as a prop and being deleted. One of the first things a new developer using Realm will do is fetch data, save it in Realm and pass it through props to another component as a master/detail. It's pretty obvious he's going to use navigation too and he won't have any idea this will break.

I don't know if there's a best practice for this case yet and I use Realm for about three years 🤔 What should we do?

Related issues: #394, #443, #1671, #2091

@tgoyne @cmelchior @kraenhansen @kneth Sorry about the flood. Just trying to raise attention.

onderonur commented 4 years ago

I have a lot of doubts about using realm.js with react-native. I agree it is fast as a lightning. But speed is not everything. Realm mutates its data (which doesn't play well with react), its listeners don't give you a lot of information about data changes (so you would maybe update your state/store using some immutable way), it messes a lot with chrome debugging and making it useless and finally this issue. I don't see a benefit of realm other than its speed. I made a demo by using realm vs TypeOrm + SQLite and yep, realm was faster. But when you use TypeOrm the right way, it really comes close to realm.

Using realm for a complex react-native project can become a million dollar mistake easily. You can't debug it (they say it works with Safari but a lot of people use Google Chrome, react-native-debugger etc.), it becomes so unpredictable because of mutations, you just try to understand what is going on by using console.warn or react-native log-android.

I think realm should have a package to integrate with react. Like mobx does. Or react-redux. Or react-apollo. They integrate your react components with the library itself. And that's it. You don't have to worry about that anymore. All the complex things to handle just abstracted away from you.

I really don't understand how so many people and blog posts just recommend realm for react-native and don't show you a good way to handle data mutations, debugging etc.

react-realm-context may look like a good package. We are using a mildly modified version of it. It definitely eases a lot of hard work for you. But this issue still stands there.

Anyway, when we come to this problem, I think using JSON.parse(JSON.stringify(...)) may cause a lot of performance problems. It may cause a lot of unnecessary re-renders. And you know, react-native needs some performance optimizations and best practices to handle complex components or lists. Or it might cause much more problems.

We are using some kind of a soft delete way. We don't delete any record from realm. We create each table with a __isDeleted field. When we delete a record, we just flip its value to true. Just filter them to not show on screens. Don't post them to your APIs. You can choose some time (like the initialization of the application) to really delete these soft deleted records.

kraenhansen commented 4 years ago

We are using a mildly modified version of it. It definitely eases a lot of hard work for you. But this issue still stands there.

@onderonur: I would love to know what mild modifications you've made, what would make the react-realm-context package better. We would love upstream PRs too :)

onderonur commented 4 years ago

We are using a mildly modified version of it. It definitely eases a lot of hard work for you. But this issue still stands there.

@onderonur: I would love to know what mild modifications you've made, what would make the react-realm-context package better. We would love upstream PRs too :)

Actually, I don't think our modifications will be useful for the package itself :) Like I said, we simply use a flag for deleted items. And we are always checking if the __isDeleted field is true or false. So instead of doing it on every single query component, our base query component filters them by default. And we added a component called "garbage cleaner" to wipe these soft deleted records out at a particular time like initialization of the app.

An additional and useful component is QueryById. It simply gets a type and a id prop to return a single record. It just wraps the Query component, you know. But it is something we use very often.

On of the changes was, we are still using the RealmProvider, but we initialize realm as a singleton and pass it to the provider. Like Provider of react-redux. We wanted to use the realm instance in places like redux action creators etc. Accessing it through the provider is a great pattern inside components. But rather than passing the realm instance as a parameter to external functions, we just wanted to use a singleton.

This might have a lot of side-effects for some cases. I wouldn't recommend to add this to the package itself. It is just something useful for our cases :)

SrikaraBhat commented 4 years ago

Even though the .isValid method is present in both collection and object, neither of them is doing anything to prevent this error.

I tried using Collection.isValid, Object.isValid, Results.snapshot .. nothing works

I'm rendering the Results on one view and if I delete one Object in another view and then return it throws this error.

photo_2017-05-24_11-37-18

here is my code for the 2 views

import React, { Component } from 'react';
import {
  InteractionManager
} from 'react-native';
import {
  Body,
  Button,
  Container,
  Content,
  Fab,
  Footer,
  FooterTab,
  Header,
  Icon,
  Input,
  Item,
  Left,
  List,
  ListItem,
  Right,
  Text,
  Title,
  View
} from 'native-base';
import realm from '../realm';

class CharactersScreen extends Component {

  constructor (props) {
    super(props);

    this.characters = realm.objects('Character');
    this.state = {
      searchBar: {
        active: false
      },
      characters: this.characters
    };
  }

  navigateToAddCharacter = () => {
    let { navigate } = this.props.navigation;

    navigate('AddCharacter');
  }

  static navigationOptions = {
    title: 'Characters',
    headerRight: <View style={{ flexDirection: 'row' }}>
      <Button transparent><Icon name="md-search"/></Button>
      <Button transparent><Icon name="md-funnel"/></Button>
    </View>
  }

  listenToRealm = (name, changes) => {
    this.setState({
      characters: this.characters
    });
  }

  componentWillMount () {
    InteractionManager.runAfterInteractions(() => {
      this.characters.addListener(this.listenToRealm);
    });
  }

  componentWillUnmount () {
    this.characters.removeListener(this.listenToRealm);
  }

  render () {
    let { navigate } = this.props.navigation;
    let {
      searchBar,
      characters
    } = this.state;

    return (
      <Container>
        {
          searchBar.active ?
            <Header searchBar rounded>
              <Item>
                <Icon name="md-search" />
                <Input placeholder="Search" />
                <Icon name="md-people" />
              </Item>
              <Button transparent>
                <Text>Search</Text>
              </Button>
            </Header> : null
        }
        <Content>
          <List>
            {
              characters.map((character, index) => {
                return (
                  <ListItem
                    key={index} onPress={navigate.bind(null, 'CharacterDetails', {
                      character
                    })}>
                    <Body>
                      <Text>{character.name}</Text>
                      <Text note>{character.note}</Text>
                    </Body>
                    <Right>
                      <Icon name="md-arrow-round-forward" />
                    </Right>
                  </ListItem>
                );
              })
            }
          </List>
        </Content>
        <Fab
          active={false}
          direction="top"
          containerStyle={{ marginLeft: 10 }}
          style={{ backgroundColor: '#5067FF' }}
          position="bottomRight"
          onPress={this.navigateToAddCharacter}>
            <Icon name="md-add" />
        </Fab>
      </Container>
    );
  }
}

export default CharactersScreen;

Here is the view where I delete the object

import React, { Component } from 'react';
import {
  Alert
} from 'react-native';
import {
  Body,
  Button,
  Card,
  CardItem,
  Container,
  Content,
  Icon,
  Left,
  Right,
  Text
} from 'native-base';
import realm from '../realm';

class CharacterDetailsScreen extends Component {

  static navigationOptions = {
    title: 'Character: Details'
  }

  promptDelete = () => {
    Alert.alert(
      'Delete Character',
      'Deleting is irreversible, are you sure?',
      [
        { text: 'Cancel', onPress: () => false },
        { text: 'OK', onPress: () => this.deleteCharacter() }
      ],
      { cancelable: false }
    );
  }

  deleteCharacter = () => {
    let {
      state: {
        params: {
          character
        }
      }
    } = this.props.navigation;
    let { goBack } = this.props.navigation;

    realm.write(() => {
      realm.delete(character);
    });

    goBack();
  }

  render () {
    let {
      navigate,
      state: {
        params: {
          character
        }
      }
    } = this.props.navigation;

    return (
      <Container>
        <Content>
          <Card>
            <CardItem header>
              <Left>
                <Icon name={character.favorite ? 'md-star' : 'md-person'}/>
                <Body>
                  <Text>{character.name}</Text>
                  <Text note>{character.note}</Text>
                </Body>
              </Left>
            </CardItem>
            <CardItem>
              <Body>
                <Text>{character.description}</Text>
              </Body>
            </CardItem>
            <CardItem>
              <Body>
                <Text>Status</Text>
                <Text note>{character.status}</Text>
              </Body>
            </CardItem>
            <CardItem>
              <Left>
                <Button onPress={this.promptDelete} transparent>
                  <Icon name="md-close" style={{ color: '#FF0000' }} />
                  <Text style={{ color: '#FF0000' }}> Delete</Text>
                </Button>
              </Left>
              <Right>
                <Button transparent>
                  <Icon name="md-create" />
                  <Text> Edit</Text>
                </Button>
              </Right>
            </CardItem>
          </Card>
        </Content>
      </Container>
    );
  }
}

export default CharacterDetailsScreen;

Solution:

Inside the constructor add one realm listener: So that, soon after change in DB, Screen Data should get refresh. Your action should contain one realm listener to monitor changes in db. Something like this:

realm.addListener('change', () => { this.loadData(); });

msharma2691 commented 4 years ago
export function convertToArray(realmObjectsArray)
{
    let copyOfJsonArray = Array.from(realmObjectsArray);
    let jsonArray = JSON.parse(JSON.stringify(copyOfJsonArray));
    return jsonArray;
}

I have used the same it is working fine but having performance issue for large data in db. Below is the code that working for me with better performance.

export async function getData(schema, filterVariables){
    return await Realm.open(databaseOption).then( async realm => {
        try {
          let data = [];
            if(filterVariables){
                data = realm.objects(schema).filtered(filterVariables);
                data =  cloneDeep(_.toArray(data));
            }else{
              data = realm.objects(schema);
              data =  cloneDeep(_.toArray(data));
            }
            realm.close();
            return {success: true, data: data};
        }
        catch (e) {
            realm.close();
            return {success: false};
        }
    })
};
BruceSuperProgramer commented 3 years ago

@vance-liu 你好,convert each RealmObject to PlainObject 如何实现?可以贴一下代码吗?被这个问题困扰好久了~

Object.assign({}, realmObject);

BruceSuperProgramer commented 3 years ago

We're almost celebrating this bug it's first birthday, and from the number of people involved here, it seems like it cost a lot of us hours of investigation, with no elegant solution. Is there any chance this one will be solved? 10x!!!

It has two years old in 2020.

techromantic commented 3 years ago

I had this problem today - deleting an object from the underlying realm store will break any flatlist relying on a list of its results.

The majority of solutions in this discussion are the wrong way of working with realm. The entire point of using a no-copy mobile database is exactly that - not to copy - converting & deconverting objects is adding extra overhead that will get worse as n gets higher.

For my specific problem i was able to skirt the issue, FlatLists can use an ExtraData prop to know when to rerender themselves. In your delete action, you must trigger the change of this extra data flag before you actually delete. Additionally you must set up your component to query realm directly when it starts up.

This is the best way to handle it for my particular problem and seems to be working well.

I also believe the listener solution would be another way to fix this.

BruceSuperProgramer commented 3 years ago

@techromantic Thanks for the nice post. I haven't tried the extra data option to solve this issue. And It also sounds a bit complex than using listeners to solve the problem from your description. I used the listener to solve this issue and it works out great.

earonesty commented 3 years ago

@techromantic your post frames the problem precisely: realm is a zero copy system... an object should be properly ref-counted when used so that it's always valid. the "correct" solution would be to a) fix realm or b) ref count the object yourself... so that deletions are "lazy" and only proceed when all refs are freed up.

AmalMenachery commented 3 years ago

@alazier / @tgoyne / @kneth / @appden - Any Updates on this one ?