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

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.

Related

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>
}

Where should "Toasts" live in Mobx State Tree?

My async actions tend to look something like this:
anAsyncAction: process(function* anAsyncAction() {
self.isLoading = true;
const service = getEnv<IMyMarksPageStoreEnv>(self).myService;
try
{
yield service.doSomething();
}
finally
{
self.isLoading = false;
}
}),
Then I let the view handle what toasts to show:
toaster = Toaster.create({
position: Position.TOP
});
render() {
return <button disabled={this.props.store.isLoading} onClick={this.handleButtonClicked}>Do Async Thing</button>
}
handleButtonClicked = () => {
const store = this.props.store;
try
{
await store.anAsyncAction();
toaster.show({ message: "Saved!", intent: Intent.SUCCESS });
}
catch(e)
{
toaster.show({ message: "Whoops an error occured: "+e, intent: Intent.DANGER });
}
}
But im starting to think that the toasts handling should live in the async try-catch of the store and not the view, but then its mixing business logic with view, so im not sure.
Any suggestions?
I'd argue that messages are part of the application.
In my app I have an array at root level
export default types.model('AppStore', {
....
flashMessages: types.optional(types.array(FlashMessage), []),
})
.actions((self) => {
/* eslint-disable no-param-reassign */
const addFlashMessage = (message) => {
self.flashMessages.push(FlashMessage.create({
...message,
details: message.details.toString(),
}));
};
function addErrorMessage(text, details = '') {
addFlashMessage({ type: 'error', text, details });
}
function addInfoMessage(text, details = '') {
addFlashMessage({ type: 'info', text, details });
}
function addSuccessMessage(text, details = '') {
addFlashMessage({ type: 'success', text, details });
}
Then
#inject('appStore')
#observer
class App extends Component {
render() {
const app = this.props.appStore;
return (
<BlockUI tag="div" blocking={app.blockUI}>
<Notifications messages={app.unseenFlashMessages} />
...
And in a component
this.props.appStore.addSuccessMessage(`User ${name} saved`);
This will also allow you to implement a 'last 5 messages' sort of thing which might be useful if you've missed a to
Guess that's not specific to mobx or mobx-state-tree, but I'd probably consider adding a dedicated NotificationStore to the picture. Service try/catch/finally would be one producer of notifications with a source of service, another might be a fetch/xhr wrapper with a source of transport.
It would be up to the business logic to decide how to present/handle those.

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

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 */
});
}

React Native graphql query with relay: fetched data not received

I have the following query:
query {
viewer{
place(id: "1a4871311fe990d6d94cf1eed9fd65008856e118b790e7dcf728d86bc3aef7ec"){
name
}
}
}
which of course works correctly on GraphiQL. I would like to use this query in a Relay container so I've created a RootContainer:
<RootContainer
Component={PlaceDetailsComponent}
route={new PlaceDetailsRoute({placeID: this.props.id})}
renderFetched={(data) => {
console.log('data: ', data);
return (
<PlaceDetailsComponent {...this.props} {...data}/>
)
} }
renderLoading={() => <ProgressBar visible={true} />}/>
which successfully fetches the data but all I can see on console is this:
data: { placeID: '1a4871311fe990d6d94cf1eed9fd65008856e118b790e7dcf728d86bc3aef7ec',
viewer:
{ __dataID__: 'Vmlld2VyLXs6aWQ9PiJ2aWV3ZXIifQ==',
__fragments__: { '1::client': [ {} ] } } }
So I checked out what's actually sent to server and what's received and all seems right to me:
Here is my route:
import Relay, {
Route
} from 'react-relay';
class PlaceDetailsRoute extends Route {
static queries = {
viewer: () => Relay.QL`
query {
viewer
}
`
}
static routeName = 'PlaceDetailsRoute'
}
export default PlaceDetailsRoute;
and here is my fragment:
Relay.createContainer(PlaceDetailsContainer, {
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
place(id: "1a4871311fe990d6d94cf1eed9fd65008856e118b790e7dcf728d86bc3aef7ec") {
name,
}
}
`
}
});
Any suggestions what should I change? Thanks in advance for any help!
That's actually expected behavior. The Relay documentation of renderFetched has a note:
Even though we have access to the data object in renderFetched, the actual data is intentionally opaque. This prevents the renderFetched from creating an implicit dependency on the fragments declared by Component.
Hope this clears up your confusion.