Relay Mutation keep giving error if static fragments is enabled - react-native

Here is my code on my mutation
class CreateUserMutation extends Relay.Mutation {
static fragments = {
user: () => Relay.QL`
fragment on User{
id
email
}
`
}
getMutation () {
return Relay.QL`mutation{ createUser }`
}
getVariables() {
return {
email: 'bondan#something.com'
}
}
getFatQuery() {
return Relay.QL`
fragment on CreateUserPayload {
user{
email
}
}
`
}
getConfigs () {
// console.log('getConfigs props', this.props);
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
user: this.props.user.id
}
}]
}
}
and this is my implementation on UI
componentWillMount () {
let mutation = new CreateUserMutation();
Relay.Store.commitUpdate(mutation);
}
its a simple experiment apps just to check if my mutation is working when the app load
however the i keep getting this error
user is not defined
Please help me what is wrong with the mutation code?

Related

Vue3: how to pass an object from provide to index

I've been using vue.js for a few weeks and I would like to understand how to globally inject to child components an object coming from the server.
When I try to inject the object using inject:['user'] to a child component it returns an empty object.
data() {
return {
user: []
}
},
methods: {
getLoggedUserData() {
axios.get('/api/get-user/' + window.auth.id
).then(response => {
this.user = response.data.user;
});
}
},
provide: {
return {
user: this.user
}
},
created() {
this.getLoggedUserData();
}
The provide option should be a function in this case to get access to this.user property:
export default {
provide() {
return {
user: this.user
}
}
}
For descendants to observe any changes to the provided user, the parent must only update user by subproperty assignment (e.g., this.user.foo = true) or by Object.assign() (e.g., Object.assign(this.user, newUserObject)):
export default {
methods: {
async getLoggedUserData() {
const { data: user } = await axios.get('https://jsonplaceholder.typicode.com/users/1')
// ❌ Don't do direct assignment, which would overwrite the provided `user` reference that descendants currently have a hold of:
//this.user = user
Object.assign(this.user, user) ✅
}
}
}
demo

Vue Apollo: How can I query GraphQL using an object as Input argument?

I would like to create a checkout object via the GraphQL API provided by the Saleor eCommerce platform.
According to the gql playground there is a mutation to do so that takes a CheckoutCreateInput object as it's argument.
Here is an example mutation that works fine within the playground.
Here is the current code that I have tried (I am doing this within a vuex action)
export const actions = {
addToCart({ commit, dispatch }, cartItem) {
const currentCartItems = this.state.cartItems
// Check to see if we already have a checkout object
if (this.state.checkoutId !== '') {
// Create a new checkout ID
console.log('creating new checkout object')
try {
this.app.apolloProvider.defaultClient
.mutate({
mutation: CREATE_CART_MUTATION,
variables: {
checkoutInput: {
lines: { quantity: 10, variantId: 'UHJvZHVjdFZhcmlhbnQ6NQ==' },
email: 'test#test.com'
}
}
})
.then(({ data }) => {
console.log(data)
})
} catch (e) {
console.log(e)
}
} else {
console.log('checkout id already set')
}
// TODO: Check to see if the cart already contains the current Cart Item
commit('ADD_CART_ITEM', cartItem)
}
and here is the CREATE_CART_MUTATION:
import gql from 'graphql-tag'
export const CREATE_CART_MUTATION = gql`
mutation($checkoutInput: CheckoutCreateInput!) {
checkoutCreate(input: $checkoutInput) {
checkout {
id
created
lastChange
lines {
id
variant {
id
name
}
quantity
totalPrice {
gross {
localized
}
net {
localized
}
}
}
totalPrice {
gross {
localized
}
net {
localized
}
}
}
}
}
`
On the server this comes back with the following error:
graphql.error.base.GraphQLError: Variable "$checkoutInput" got invalid value {"email": "test#test.com", "lines": {"quantity": 10, "variantId": "UHJvZHVjdFZhcmlhbnQ6NQ=="}}.
In field "lines": In element #0: Expected "CheckoutLineInput", found not an object.
Looks like I was most of the way there, I was just passing a single lines object rather than an array of them. The correct code is as follows:
try {
this.app.apolloProvider.defaultClient
.mutate({
mutation: CREATE_CART_MUTATION,
variables: {
checkoutInput: {
lines: [
{ quantity: cartItem.quantity, variantId: cartItem.variantId }
],
email: 'test#test.com'
}
}
})
.then(({ data }) => {
console.log('mutation done!')
commit('SET_CHECKOUT_OBJECT', data.checkoutCreate.checkout)
})
} catch (e) {
console.log('error:')
console.log(e)
}

Deep links in react-native-firebase notifications

I am using react-native-firebase with messaging to deliver notifications to my app with cloud functions, with admin.messaging().send(message), very similar to here: https://medium.com/the-modern-development-stack/react-native-push-notifications-with-firebase-cloud-functions-74b832d45386 .
I receive notifications when the app is in the background. Right now I am sending a text in the body of the notification, like 'a new location has been added to the map'. I want to be able to add some sort of deep link, so that when I swipe View on the notification (on iOS for example), it will take me to a specific screen inside the app. How do I pass data from the notification to the app?
I am using react-native-navigation in the app. I can only find code about deep links from inside the app (https://wix.github.io/react-native-navigation/#/deep-links?id=deep-links).
My solution was to use add what information I need in the data object of the notification message object:
in functions/index.js:
let message = {
notification: {
body: `new notification `
},
token: pushToken,
data: {
type: 'NEW_TRAINING',
title: locationTitle
}
};
and process as follows in the app for navigation:
this.notificationOpenedListener =
firebase.notifications().onNotificationOpened((notificationOpen: NotificationOpen) => {
if (notification.data.type === 'NEW_TRAINING') {
this.props.navigator.push({
screen: 'newtrainingscreen',
title: notification.data.title,
animated: true
});
}
I think you are fine with the "how firebase notification work"... cause of this, here is only an description of the Logic how you can Deeplinking into your App.
If you send a notification, add a data-field. Let's say your app has a Tab-Navigator and the sections "News","Service" and "Review".
In your Push-Notification - Datafield (let's name it "jumpToScreen" you define your value:
jumpToScreen = Service
I assume you still have the Handling to recieve Notifications from Firebase implemented.
So create an /lib/MessageHandler.js Class and put your business-logic inside.
import firebase from 'react-native-firebase';
/*
* Get a string from Firebase-Messages and return the Screen to jump to
*/
const getJumpPoint = (pointer) => {
switch (pointer) {
case 'News':
return 'NAV_NewsList'; // <= this are the names of your Screens
case 'Service':
return 'NAV_ServiceList';
case 'Review':
return 'NAV_ReviewDetail';
default: return false;
}
};
const MessageHandler = {
/**
* initPushNotification initialize Firebase Messaging
* #return fcmToken String
*/
initPushNotification: async () => {
try {
const notificationPermission = await firebase.messaging().hasPermission();
MessageHandler.setNotificationChannels();
if (notificationPermission) {
try {
return await MessageHandler.getNotificationToken();
} catch (error) {
console.log(`Error: failed to get Notification-Token \n ${error}`);
}
}
} catch (error) {
console.log(`Error while checking Notification-Permission\n ${error}`);
}
return false;
},
clearBadges: () => {
firebase.notifications().setBadge(0);
},
getNotificationToken: () => firebase.messaging().getToken(),
setNotificationChannels() {
try {
/* Notification-Channels is a must-have for Android >= 8 */
const channel = new firebase.notifications.Android.Channel(
'app-infos',
'App Infos',
firebase.notifications.Android.Importance.Max,
).setDescription('General Information');
firebase.notifications().android.createChannel(channel);
} catch (error) {
console.log('Error while creating Push_Notification-Channel');
}
},
requestPermission: () => {
try {
firebase.messaging().requestPermission();
firebase.analytics().logEvent('pushNotification_permission', { decision: 'denied' });
} catch (error) {
// User has rejected permissions
firebase.analytics().logEvent('pushNotification_permission', { decision: 'allowed' });
}
},
foregroundNotificationListener: (navigation) => {
// In-App Messages if App in Foreground
firebase.notifications().onNotification((notification) => {
MessageHandler.setNotificationChannels();
navigation.navigate(getJumpPoint(notification.data.screen));
});
},
backgroundNotificationListener: (navigation) => {
// In-App Messages if App in Background
firebase.notifications().onNotificationOpened((notificationOpen) => {
const { notification } = notificationOpen;
notification.android.setChannelId('app-infos');
if (notification.data.screen !== undefined) {
navigation.navigate(getJumpPoint(notification.data.screen));
}
});
},
appInitNotificationListener: () => {
// In-App Messages if App in Background
firebase.notifications().onNotificationOpend((notification) => {
notification.android.setChannelId('app-infos');
console.log('App-Init: Da kommt ne Message rein', notification);
firebase.notifications().displayNotification(notification);
});
},
};
export default MessageHandler;
In your index.js you can connect it like this:
import MessageHandler from './lib/MessageHandler';
export default class App extends Component {
state = {
loading: null,
connection: null,
settings: null,
};
async componentDidMount() {
const { navigation } = this.props;
await MessageHandler.initPushNotification();
this.notificationForegroundListener = MessageHandler.foregroundNotificationListener(navigation);
this.notificationBackgroundListener = MessageHandler.backgroundNotificationListener(navigation);
this.setState({ loading: false, data });
}
componentWillUnmount() {
this.notificationForegroundListener();
this.notificationBackgroundListener();
}
async componentDidMount() {
MessageHandler.requestPermission();
AppState.addEventListener('change', this.handleAppStateChange);
MessageHandler.clearBadges();
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
}
handleAppStateChange = (nextAppState) => {
if (nextAppState.match(/inactive|background/)) {
MessageHandler.clearBadges();
}
....
I hope this give you an Idea how to implement it for your needs.
I think you don't need to use deep links nor dynamic links but just use Firebase/Notifications properly. If I were you I would add the following logic in the componentDidMount method of your parent container:
async componentDidMount() {
// 1. Check notification permission
const notificationsEnabled = await firebase.messaging().hasPermission();
if (!notificationsEnabled) {
try {
await firebase.messaging().requestPermission(); // Request notification permission
// At this point the user has authorized the notifications
} catch (error) {
// The user has NOT authorized the notifications
}
}
// 2. Get the registration token for firebase notifications
const fcmToken = await firebase.messaging().getToken();
// Save the token
// 3. Listen for notifications. To do that, react-native-firebase offer you some methods:
firebase.messaging().onMessage(message => { /* */ })
firebase.notifications().onNotificationDisplayed(notification => { /* */ })
firebase.messaging().onNotification(notification => { /* */ })
firebase.messaging().onNotificationOpened(notification => {
/* For instance, you could use it and do the NAVIGATION at this point
this.props.navigation.navigate('SomeScreen');
// Note that you can send whatever you want in the *notification* object, so you can add to the notification the route name of the screen you want to navigate to.
*/
})
}
You can find the documentation here: https://rnfirebase.io/docs/v4.3.x/notifications/receiving-notifications

Computed Getter causes maximum stack size error

I'm trying to implement the following logic in Nuxt:
Ask user for an ID.
Retrieve a URL that is associated with that ID from an external API
Store the ID/URL (an appointment) in Vuex
Display to the user the rendered URL for their entered ID in an iFrame (retrieved from the Vuex store)
The issue I'm currently stuck with is that the getUrl getter method in the store is called repeatedly until the maximum call stack is exceeded and I can't work out why. It's only called from the computed function in the page, so this implies that the computed function is also being called repeatedly but, again, I can't figure out why.
In my Vuex store index.js I have:
export const state = () => ({
appointments: {}
})
export const mutations = {
SET_APPT: (state, appointment) => {
state.appointments[appointment.id] = appointment.url
}
}
export const actions = {
async setAppointment ({ commit, state }, id) {
try {
let result = await axios.get('https://externalAPI/' + id, {
method: 'GET',
protocol: 'http'
})
return commit('SET_APPT', result.data)
} catch (err) {
console.error(err)
}
}
}
export const getters = {
getUrl: (state, param) => {
return state.appointments[param]
}
}
In my page component I have:
<template>
<div>
<section class="container">
<iframe :src="url"></iframe>
</section>
</div>
</template>
<script>
export default {
computed: {
url: function (){
let url = this.$store.getters['getUrl'](this.$route.params.id)
return url;
}
}
</script>
The setAppointments action is called from a separate component in the page that asks the user for the ID via an onSubmit method:
data() {
return {
appointment: this.appointment ? { ...this.appointment } : {
id: '',
url: '',
},
error: false
}
},
methods: {
onSubmit() {
if(!this.appointment.id){
this.error = true;
}
else{
this.error = false;
this.$store.dispatch("setAppointment", this.appointment.id);
this.$router.push("/search/"+this.appointment.id);
}
}
I'm not 100% sure what was causing the multiple calls. However, as advised in the comments, I've now implemented a selectedAppointment object that I keep up-to-date
I've also created a separate mutation for updating the selectedAppointment object as the user requests different URLs so, if a URL has already been retrieved, I can use this mutation to just switch the selected one.
SET_APPT: (state, appointment) => {
state.appointments = state.appointments ? state.appointments : {}
state.selectedAppointment = appointment.url
state.appointments = { ...state.appointments, [appointment.appointmentNumber]: appointment.url }
},
SET_SELECTED_APPT: (state, appointment) => {
state.selectedAppointment = appointment.url
}
Then the getUrl getter (changed its name to just url) simply looks like:
export const getters = {
url: (state) => {
return state.selectedAppointment
}
}
Thanks for your help guys.

Realm "observer.next create #[native code]" exception

I am trying to fetch data with apollo and then write it to realm. I have created a js file that I know works, because it has worked before. But, when I try to write to a particular model I get an error message. More details as follows:
Code (Not entire code) LocationQuery.js:
const realm = new Realm({ schema: [testBuilding1], schemaVersion: 1 });
let buildingTypeArray = [];
const temp = [];
class LocationQuery extends Component {
static get propTypes() {
return {
data: React.PropTypes.shape({
loading: React.PropTypes.bool,
error: React.PropTypes.object,
sites: React.PropTypes.array,
}).isRequired,
};
}
render() {
if (this.props.data.loading) {
return (null);
}
if (this.props.data.error) {
return (<Text>An unexpected error occurred</Text>);
}
if (this.props.data.sites) {
this.props.data.sites.map((value) => {
buildingTypeArray.push(value.locations);
});
buildingTypeArray.forEach((locationValues) => {
realm.write(() => {
realm.create('testBuilding1', {
building: '273',
});
});
});
}
return null;
}
}
const locationQueryCall = gql`
query locationQueryCall($id: String!){
sites(id: $id){
locations {
building
type
}
}
}`;
const ViewWithData = graphql(locationQueryCall, {
options: props => ({
variables: {
id: 'SCH1',
},
}),
})(LocationQuery);
export default connect(mapStateToProp)(ViewWithData);
The error I get is a big red screen that read:
console.error: "Error in observe.next.... blah blah blah"
The Model I am using:
export const testBuilding1 = {
name: 'testBuilding1',
properties: {
building: 'string',
},
};
The weird thing is that the code works when I use this model:
export const locationScene = {
name: 'locationScene',
properties: {
building: 'string',
},
};
I am calling LocationQuery.js in another piece of code passing it through at render.
Thank you in advance for the help!