realm.js - Accessing object of type Contact which has been invalidated or deleted with NO stack trace - react-native

Would (really) appreciate help on this one.
I have a realm listView which navigates away to a detailed view.
The detailed view is deleting the entry from the original list view and navigate back. I have registered listeners on the realm DB change to update the content of the list view.
Here is the code I'm using, which, after delete get an exception - "Accessing object of type Contact which has been invalidated or deleted" after the navigation occurs.
Does anyone have an idea why?
Also, it seems that the change listener (updateContactsFromDB) is called twice, while deleting just one object - ideas?
10x
ContactPage.js:
export default class ContactsPage extends Component {
updateContactsFromDB(){
console.log("ContactsPage:updateContactsFromDB()");
let contacts = Realm.objects('Contact');
this.setState({
dataSource: this.state.dataSource.cloneWithRows(contacts.snapshot()),
});
}
constructor(props) {
console.log("ContactsPage:constructor()");
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => (r1 !== r2)});
let contacts = Realm.objects('Contact');
this.state = {
dataSource: ds.cloneWithRows(contacts.snapshot()),
};
this.updateContactsFromDB = this.updateContactsFromDB.bind(this);
}
componentWillMount(props){
console.log("ContactsPage:componentWillMount");
Realm.addListener('change', this.updateContactsFromDB);
}
componentWillUnmount(props){
console.log("ContactsPage:componentWillUnmount");
Realm.removeListener('change', this.updateContactsFromDB);
}
render() {
console.log("ContactsPage:render()");
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(contact) => (
<TouchableOpacity onPress={ () => this.props.navigation.navigate('ContactNotesPage', { contact: contact}) }>
<Text>test Contact</Text>
</TouchableOpacity>
)
}
/>
);
}
}
ContactNotesPage.js:
export default class ContactNotesPage extends Component {
constructor(props) {
console.log("ContactNotesPage:constructor");
super(props);
}
render(){
console.log("ContactNotesPage:render()");
const { params } = this.props.navigation.state;
return (
<TouchableOpacity onPress={ () => {
console.log("ContactNotesPage:delete");
Realm.write(() => { Realm.delete(params.contact);});
this.props.navigation.navigate('ContactsPage');
}
}>
<Text>DeleteContact</Text>
</TouchableOpacity>
)
}
};
// main.js
const MainStack = StackNavigator({
ContactsPage: {
screen: ContactsPage,
},
ContactNotesPage:{
screen: ContactNotesPage,
},
});
export default MainStack;

Seems like it's a bug in realm/react-navigation. when passing a parameter which is a realm object - if you delete this object your next navigation will fail.
A bug is already open at https://github.com/realm/realm-js/issues/1031

This happens when you get an object from realm (using realm.objects('collection')) and use realm.close() function on it. Either use Object.assign method or destructuring object or array approach to mitigate this problem. Like below,
const realmObject = realm.objects('collection');
const obj = [...realmObject] or {...realmObject[0]}; // the later one when filtered is used on realmObject.
My solution was that I did not call realm.close() because I was constantly engaged with the database.
https://realm.io/docs/javascript/0.14.0/api/Realm.html#close

i used useFocusEffect from #react-navigation/native after delete item in array for update list array like this
//state to check get data ability:
const [getDataAbility, setGetDataAbility] = useState(true);
//useFocusEffect
useFocusEffect(
React.useCallback(() => {
if (getDataAbility === true) {
// handle data
}
}, [ dependencies... ]),
);
// catch error ""accessing obj of..."
try {
console.log(pickingItem.id);
} catch (error) {
setGetDataAbility(false);
console.log('err picking: ', error);
}
-> solve

This is work for me:
async function updateRealm() {
const realm = Realm.open({
schemas: [
/*myschemas*/
]
});
realm.addListener('change', () => {
/* consult function */
});
}

Related

removeEventListener is Deprecated and i don't achieve to refacto it properly

Linking.removeEventListener('url', onReceiveURL);
removeEventListener is deprecated.
This is what my IDE suggests :
EventEmitter.removeListener('url', ...): Method has been deprecated.
Please instead use remove() on the subscription returned by
EventEmitter.addListener.
// Custom function to subscribe to incoming links
subscribe(listener: (deeplink: string) => void) {
// First, you may want to do the default deep link handling
const onReceiveURL = ({url}: {url: string}) => listener(url);
// Listen to incoming links from deep linking
Linking.addEventListener('url', onReceiveURL);
const handleDynamicLink = (
dynamicLink: FirebaseDynamicLinksTypes.DynamicLink,
) => {
listener(dynamicLink.url);
};
const unsubscribeToDynamicLinks = dynamicLinks().onLink(handleDynamicLink);
return () => {
unsubscribeToDynamicLinks();
Linking.removeEventListener('url', onReceiveURL);
};
I tried many things but nothing seems to work.
Didn't find any concrete information about it.
Any help to figure it out ?
EDIT -> I will investigate further but so far it's working :
const unsubscribeToDynamicLinks : any = ...
then in return :
return () => {
unsubscribeToDynamicLinks().remove('url', onReceiveURL);};
For the new API, this is how it works:
useEffect (() => {
const subscription = Linking.addEventListener('url', onReceiveURL);
return () => subscription.remove();
}, [])
For class components you can use something like below:
class MyClass extends Component {
constructor(props){
super(props)
this.changeEventListener = null
}
componentDidMount(){
// or you can use constructor for this
this.changeEventListener = AppState.addEventListener('change', () => {
// your listener function
})
}
componentWillUnmount() {
this.changeEventListener.remove()
}
}

React-admin - Cannot update a component (`xxx`) while rendering a different component (`SelectInput`)

React-admin 3.8.4
I'm rendering some form fields conditionally, and these fields have some validations. Because of this, I'm receiving this error below:
Warning: Cannot update a component (nameOfTheComponent) while rendering a different component
(SelectInput). To locate the bad setState() call inside SelectInput, follow the stack trace
as described in...
I already have read some explanation about the problem and I've discovered that react-final-form
calls a setState() when registering those fields and this seems to be the issue.
I also saw that there is a fix into FileConfig called silent that solves this problem React final form silent
But I don't know if I'm using wrong, because the warning remains showing up.
I'm trying to do something like this:
const OfferVariation = ({ formData, ...rest }) => {
const form = useForm();
useEffect(() => {
return () => {
const initialState = {}
let inConstructor = true
const fieldName = "internalOffer.type"
form.registerField(fieldName, fieldState => {
if (inConstructor) {
initialState[fieldName] = fieldState
} else {
this.setState({ [fieldName]: fieldState })
}
}, { silent: true })
}
}, [])
if (flowType === "Interna") {
return (
<SelectInput
source="internalOffer.type"
label="Tipo da Oferta"
choices={offerTypes}
validate={validateArrayNotEmpty}
{...rest}
/>
)
} else if (flowType === "Externa") {
return (
<TextInput
label="Url Externa"
source="externalOffer.externalURL"
{...rest}
/>
)
}
}
};
export default OfferVariation;
Does anyone know how to fix it and could help me?

TypeError: Undefined is not a function (evaluating 'this.state...map')

**In this my code, I got an error which says 'this.state.leader.map' is not a function.
I searched on web about my problem but none of them solved my issue.
This is my code which is below. I hope, code is clear to understand easly**
import React, { Component } from 'react';
import { View, Text, FlatList } from 'react-native';
import DOMParser from 'react-native-html-parser';
import axios from 'axios';
export default class header extends Component {
state = { leader: [] };
componentWillMount() {
fetch('url')
.then(response => {
if (response.ok) {
return response;
}else {
let error = new Error('Error ');
error.response = response;
throw error;
}
},
error => {
let errmess = new Error(error.message);
throw errmess;
})
.then(response => response.text())
.then(leaders => {
const str = leaders.substring(76);
const str2 = str.substring(0, str.length - 9);
this.setState({ leader: str2 });
})
.catch(error => {
this.setState({ errMessage: error.message });
});
}
renderall() {
return this.state.leader.map(alb => <Text>{alb.Ref}</Text>
}
render() {
console.log(this.state.leader);
return (
<View>
{this.renderall()}
</View>
);
}
}
You issue is with the fact that you are setting a string to the value of leader in your state.
If we look at your code you are taking a substring of leaders. To do this leaders must be a string. You are then saving that string to your leader value in state.
.then(leaders => {
const str = leaders.substring(76);
const str2 = str.substring(0, str.length - 9); // <- this is a string
this.setState({ leader: str2 });
})
In your state you are setting leaders in the following way
state = {
leaders: [] // <- this is an array
}
Individually doing these things are fine, but there is a disconnect between what you are doing in your fetch request and what you then want to do in your renderAll method as you cannot use .map on a string.
renderall() {
return this.state.leader.map(alb => <Text>{alb.Ref}</Text>
// .map requires this.state.leader to be an array but you
// have now changed it to a string so it won't work
}
Either you need to change what you are storing in leader in your fetch request so that it is an array. Or you need to change what is happening in your renderAll function.
XML parsing
If your XML is the same as you put in your comment
<?xml version="1.0" encoding="utf-8"?>
<string xmlns="url">[{"Ref":"IHR1900299","Unvan":"max"},{"Ref":"IHR1900298","Unvan":"max2"}] </string>
Then you could use a simple regex to capture what you want and then use JSON.parse to convert it into json.
Once you have your xml as a string, you could do this:
// I'm hardcoding it as a string, because I don't have your api to call.
let xmlString = '<?xml version="1.0" encoding="utf-8"?> <string xmlns="url">[{"Ref":"IHR1900299","Unvan":"max"},{"Ref":"IHR1900298","Unvan":"max2"}] </string>';
let jsonString = xmlString.match(/\[.*\]/);
let json = JSON.parse(jsonString);
console.log(json)
console.log(json[0].Ref)
React component usual methods (like componentWillMount or render) are automatically bound to this by React, contrary to your custom method renderAll. Add the following to your class:
constructor(props) {
super(props);
this.renderAll = this.renderAll.bind(this);
}
Useful links:
Docs on the bind keyword
Binding to this in React
Related question: React Native: bind and 'this'?
Related question: How to bind(this) when passing a function as a prop in react native?
This happen because the fetch function is asynchronous. So, all you need is to add a simple condition and passing this:
renderall() {
return this.state.leader.map(alb => <Text>{alb.Ref}</Text>
}
render() {
console.log(this.state.leader);
return (
<View>
{
(this.state.leader?this.state.leader.length>0:false)&&
this.renderall(this)
}
</View>
);
}
Your renderAll method needs to return a single JSX element in order to be correct. Additionally I reformatted the method to use arrow functions to avoid all those nasty binding issues. Also: you need to provide a unique key to each of the items.
renderall = () => {
return (
<View>
{this.state.leader.map(alb => {
return <Text key={alb.Ref}>{alb.Ref}</Text>;
})}
</View>
}

Force refetch in Relay Modern RefetchContainer with no (new) variables

I'm trying to find out how/if it is possible to trigger a refresh in a Relay Modern RefreshContainer without passing (new) variables?
I’m looking for the best way to implement the good ol’ pull-to-refresh on a React Native list, that should simply refetch the original query - no variables needed?
According to docs (https://facebook.github.io/relay/docs/api-cheatsheet.html) this should be possible using
this.props.relay.refetch({}, callback, {force: true})
but I get an error saying "undefined is not an object ('evaluating taggedNode.modern')"
The query works just fine if I use a plain old FragmentContainer instead, but I'd just like a simple pull-to-refresh functionality :-)
EDIT
Adding more code for clarity. Also updated call to reflect change to API that includes render variables, passing null
class HistoryList extends React.PureComponent<void, Props, State> {
state = { refreshing: false };
_renderRow = ({ item }) => {
return <HistoryListItem item={item.node} />;
};
_renderHeader = ({ section }) => {
return (
<Text style={[cs.breadText, _styles.sectionHeader]}>
{section.title}
</Text>
);
};
_onRefresh = () => {
this.setState({ refreshing: true });
this.props.relay.refetch({}, null, this._onRefreshDone(), { force: true });
};
_onRefreshDone = () => {
this.setState({ refreshing: false });
};
_sortDataIntoSections = (edges: Array<Node>) => {
return _.chain(edges)
.groupBy(element => {
return moment(element.node.huntDate).format('MMMM YYYY');
})
.map((data, key) => {
return { title: key, data: data };
})
.value();
};
render() {
return (
<View style={_styles.container}>
<SectionList
renderSectionHeader={this._renderHeader}
sections={this._sortDataIntoSections(
this.props.entries.allJournalEntries.edges
)}
renderItem={this._renderRow}
keyExtractor={(item, index) => item.node.__id}
onRefresh={this._onRefresh}
refreshing={this.state.refreshing}
/>
</View>
);
}
}
export default createRefetchContainer(
HistoryList,
graphql`
fragment HistoryList_entries on Viewer {
allJournalEntries(orderBy: huntDate_DESC) {
count
edges {
node {
huntDate
...HistoryListItem_item
}
}
}
}
`
);
It seems that the arguments of this.props.relay.refetch has been change to refetch(refetchVariables, renderVariables, callback, options) (in https://facebook.github.io/relay/docs/refetch-container.html) and the cheetsheet has been out-of-date.
I'm not sure that in which version that this has change, but you can give it a try and change your code to:
this.props.relay.refetch({}, null, callback, {force: true})
A solution has been found by robrichard at github.
I was missing the third argument for the RefetchContainer, which is the query to execute on refetch. This, combined with the suggestion from #zetavg was what was needed.
The exported module now looks like this:
export default createRefetchContainer(
HistoryList,
{
entries: graphql`
fragment HistoryList_entries on Viewer {
allJournalEntries(orderBy: huntDate_DESC) {
count
edges {
node {
huntDate
...HistoryListItem_item
}
}
}
}
`
},
graphql`
query HistoryListRefetchQuery {
viewer {
...HistoryList_entries
}
}
`
);
I have both solution applied (query for refetch and relay refetch call).
Refetch query
(do not pay attention at fact, that I didn't specify a component for container, there is special decorator in our code base for it):
{
viewer: graphql`
fragment RatingSchoolsTableContainer_viewer on Viewer {
rating {
schools {
uid
masterUrl
paidOrderSum
paidOrderCount
averageReceipt
}
}
}
`,
},
graphql`
query RatingSchoolsTableContainer_RefetchQuery {
viewer {
...RatingSchoolsTableContainer_viewer
}
}
`,
And relay call:
this.props.relay?.refetch({}, null, () => {}, {force: true})
There is no re-render anyway, but I have new response from server in network.

How to render component with ajax in react native?

My code is
const main = () => {
let caption;
AsyncStorage.getItem("XXX", (err, result) => {
caption = <View>...</View>
});
render (
...
{caption}
...
);
}
But I got an error as below.
RawText "" must be wrapped in an explicit <Text> component.
I'm going to assume that, based on your pseudo-code, you understand how to get data from AsyncStorage, that it's not a good idea to be using AsyncStorage inside your render function, and that you don't actually mean ajax but rather local storage.
But the error is showing up because you need to make sure you wrap text inside a <Text> element. If you look at this paragraph it says:
In React Native, we are more strict about it: you must wrap all the text nodes inside of a <Text> component; you cannot have a text node directly under a <View>.
EDIT:
class MyComponent extends Component {
constructor(props) {
super(props);
this.state = {
data: '',
};
}
componentDidMount() {
AsyncStorage.getItem('XXX', (err, result) => {
// #TODO: You should handle errors too
this.setState({
data: result.text,
});
});
}
render() {
// Returning null will not render anything
// Once the results come in, it will update automatically
if (!this.state.data) return null;
// Raw text must be wrapped in Text
return (
<Text>{this.state.data}</Text>
);
}
}
You can try to stock data in a state and display your component whit a function:
AsyncStorage.getItem("XXX", (err, result) => {
this.setState({result:result})
});
function element(){
if([check if your state is not null])
return(
<View>... {this.state.result} ...</View>
)
}
render (
...
{this.element()}
...
);