Relay subscriptions not working with react-native - react-native

I'm using Express Graphql server with react native and Relay. My device does connects to the subscription but it does not subscribe to it. Here's my index.js on the server
const subscriptionServer = SubscriptionServer.create(
{
execute,
subscribe,
schema,
onOperation: (message, params, webSocket) => {
console.log(params)
return params;
},
onConnect: () => {
// My device does connects
console.log("client connected")
}
},
{
server,
path: '/subscriptions'
},
);
app.use('/graphql', graphqlHTTP({
schema,
graphiql: true
}));
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
subscriptionsEndpoint: `ws://127.0.0.1:8080/subscriptions`
}));
server.listen(PORT, ()=> {
console.log("Groceries running on port " + PORT)
console.log(
`subscriptions is now running on ws://localhost:${PORT}/subscriptions'}`
);
});
The resolver for subscription on the server, it was quite troublesome to figure out since everyone is using executable schema from apolloGraphql.
export default {
type: OrderEdges,
args: {
ShopId: {type: GraphQLID},
},
subscribe: withFilter(() => pubsub.asyncIterator('orderConfirmed'), (payload, variables) => {
console.log(payload)
console.log(variables)
return payload.orderConfirmed.node.ShopId == variables.ShopId;
}),
}
Now the react-native client. My subscription setup with relay environment.
const setupSubscriptions = (config, variables, cacheConfig, observer) => {
const query = config.text; //does console logs the query
const subscriptionClient = new SubscriptionClient(`ws://192.168.0.100:8080/subscriptions`, {reconnect:true});
subscriptionClient.request({query, variables}, (err, result) => {
console.log(err) // doesn't get call inside the request method
observer.onNext(data:result)
})
}
My subscription method,
export default function() {
const variables = {
ShopId: shop.getShop()[0].id
}
requestSubscription(
environment,
{
subscription,
variables,
onCompleted: (res, err) => {
console.log(res)
console.log(err)
},
updater: (store) => {...},
onError: error => console.error(error),
onNext: (response) => {console.log(response)}
});
}
the component where I'm calling to subscribe,
import subscription from '../../GraphQLQueries/subscriptions/orderConfirmed';
class OrdersBox extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
//initializing subscription
orderSubscriptions();
}
When the device starts the app, my device is connected to the web socket as I can see the console.log statement inside the onConnect method in SubscriptionServer. But when the payload is published after a mutation, the subscribe method doesn't get called. I can't seem to figure out what I'm doing wrong. Maybe it's some react-native specific config that I'm missing cuz everything seems to work fine when I test it on graphiql.
I can't find any example of react-native and relay subscriptions used with express graphql.
note: Everything is working when I use subscription with graphiql. But not with react-native and relay.
Thanks in advance guys
....

I wasn't returning the subscriptionClient.request method. Adding a return statement solved the problem. You don't have to return when using subscribe method in subscriptions-transport-ws#0.8.3. But version 0.9.1 replaces the subscribe function with request which does require it to return.

try:
function setupSubscription(config, variables, cacheConfig, observer) {
const query = config.text;
const subscriptionClient = new SubscriptionClient(websocketURL, {
reconnect: true
});
const client = subscriptionClient.request({ query, variables }).subscribe({
next: result => {
observer.onNext({ data: result.data });
},
complete: () => {
observer.onCompleted();
},
error: error => {
observer.onError(error);
}
});
return {
dispose: client.unsubscribe
};
}
subscriptions-transport-ws#0.9.1

Related

How to access object on Vue front end from axios proxy

I have a locally hosted mongodb database with mongoose, express, axios, and a Vue front end. Right now I'm trying to access a single object from an exported array, but I'm missing the mark and getting "undefined" as the result.
vue.config.js:
module.exports = {
devServer: {
proxy: 'http://localhost:3000',
}
}
here's the front end Vue script meant to use the objects:
import axios from 'axios';
export default {
name: 'Game',
data () {
return {
pages: [],
currentPage: {},
pageTitle: "",
pageText: "",
options: [],
}
},
created () {
this.getPages();
},
methods: {
async getPages() {
try {
let res = await axios.get('/api/pages');
this.pages = res.data;
console.log(this.pages);
this.currentPage = this.pages[0];
console.log(this.currentPage);
return true;
} catch (error) {
console.log(error);
}
},
my "get" endpoint in pages.js:
router.get('/', async (req, res) => {
try {
let pages = await Page.find();
res.send({pages: pages}); //send result of search for pages as list of pages called "pages"
} catch (error) {
console.log(error);
res.sendStatus(500); //500 = server could not fulfill request
}
});
the route in server.js:
const pages = require('./routes/pages');
app.use('/api/pages', pages);
app.listen(3000, () => console.log('Server listening on port 3000!'));
module.exports = app;
and here's the console output, with the "pages" object from vue's data property and the "currentPage" that's supposed to be at pages[0] (printed to console in earlier example):
I can access the api at 'localhost:3000/api/pages' just fine, but how do I break into that array and access the first page object? I want to get an object from the list axios fetches from mongoose, then hold that object in a variable so I can access it's properties. The whole "pages > [[Target]] > pages > [ ]" is part of the problem I'm sure, but I don't know what to tell the code to open it.
Whoops! I realized my mistake. In pages.js I should have sent "res.send(pages);" After a whole couple days too XD

relay subscription onNext not triggered on react-native

I am a subscription setup but onNext is not getting triggered I am not sure why since this is my first time implementing subscription and docs was not much help with the issue.
Here are the code implementations:
import {
graphql,
requestSubscription
} from 'react-relay'
import environment from '../network';
const subscription = graphql`
subscription chatCreatedSubscription{
chatCreated{
id
initiate_time
update_time
support_id
category_id
email
name
}
}
`;
function chatCreated(callback) {
const variables = {};
requestSubscription(environment, {
subscription,
variables,
onNext: () => {
console.log("onNext");
callback()
},
updater: () => {
console.log("updater");
}
});
}
module.exports = chatCreated;
and here is my network for the subscription
import { Environment, Network, RecordSource, Store } from "relay-runtime";
import Expo from "expo";
import { SubscriptionClient } from "subscriptions-transport-ws";
import { WebSocketLink } from 'apollo-link-ws';
import { execute } from 'apollo-link';
import accessHelper from "../helper/accessToken";
const networkSubscriptions = async (operation, variables) => {
let token = await accessHelper();
if (token != null || token != undefined) {
const subscriptionClient = new SubscriptionClient("ws://localhost:3000/graphql",
{
reconnect: true,
connectionParams: {
Authorization: token,
},
});
execute(new WebSocketLink(subscriptionClient), {
query: operation.text,
variables,
});
}
}
const network = Network.create(fetchQuery, networkSubscriptions);
const store = new Store(new RecordSource());
const environment = new Environment({
network,
store
});
export default environment;
the subscription is called in a componentDidMount method on a component it executes but the onNext method inside the subscription is never triggered when new information is added to what the subscription is listening to.
so i figured out that my issue was the network js not being setup properly and the version of subscription-transport-ws. i added version 0.8.3 of the package and made the following changes to my network file:
const networkSubscriptions = async (config, variables, cacheConfig, observer) => {
const query = config.text;
let token = await accessHelper();
if (token != null || token != undefined) {
const subscriptionClient = new SubscriptionClient(`ws://${api}/graphql`,
{
reconnect: true,
connectionParams: {
Authorization: token,
},
});
subscriptionClient.subscribe({ query, variables }, (error, result) => {
observer.onNext({ data: result })
})
return {
dispose: subscriptionClient.unsubscribe
};
}
}
i hope this helps you if you get stuck with the same issue as mine.

How Can I pass params with an API client to vue-head?

I am passing params from my API to vue-head but every time I do that it send me undefined in the head this is the code:
export default {
data: () => ({
errors: [],
programs: [],
}),
methods: {
getProgram() {
this.api.http.get(`videos/program/${this.programSlug}`)
.then(response => {
this.programs = response.data
})
.catch(error => {
this.errors = error
});
}
},
head: {
title: function() {
return {
inner: this.programs.name,
separator: '|',
complement: 'Canal 10'
};
}
}
}
any idea what I am doing wrong with my code??
First verify you are fetching the information correctly. Use console log and go to network tab and verify you are fetching the data correct, you might have to comment out vue-head. But what I think is that the problem might be due to vue-head rendering before the api call finishes then no data is being passed.
If you are using vue-router this can be easily solved with beforeRouteEnter() hook. But if not! apparently vue-head has an event that you can emit to update the component after render.
I haven't tried this but it should work. you can add the function below to your methods and call it after the promise is resolved i.e in the then closure.
methods: {
getProgram() {
this.api.http.get(`videos/program/${this.programSlug}`)
.then(response => {
this.programs = response.data
this.$emit('updateHead')
})
.catch(error => {
this.errors = error
});
}
}

Unit testing HTTP request with Vue, Axios, and Mocha

I'm really struggling trying to test a request in VueJS using Mocha/Chai-Sinon, with Axios as the request library and having tried a mixture of Moxios and axios-mock-adaptor. The below examples are with the latter.
What I'm trying to do is make a request when the component is created, which is simple enough.
But the tests either complain about the results variable being undefined or an async timout.
Am I doing it right by assigning the variable of the getData() function? Or should Ireturn` the values? Any help would be appreciated.
Component
// Third-party imports
import axios from 'axios'
// Component imports
import VideoCard from './components/VideoCard'
export default {
name: 'app',
components: {
VideoCard
},
data () {
return {
API: '/static/data.json',
results: null
}
},
created () {
this.getData()
},
methods: {
getData: function () {
// I've even tried return instead of assigning to a variable
this.results = axios.get(this.API)
.then(function (response) {
console.log('then()')
return response.data.data
})
.catch(function (error) {
console.log(error)
return error
})
}
}
}
Test
import Vue from 'vue'
import App from 'src/App'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
let mock = new MockAdapter(axios)
describe('try and load some data from somewhere', () => {
it('should update the results variable with results', (done) => {
console.log('test top')
mock.onGet('/static/data.json').reply(200, {
data: {
data: [
{ id: 1, name: 'Mexican keyboard cat' },
{ id: 2, name: 'Will it blend?' }
]
}
})
const VM = new Vue(App).$mount
setTimeout(() => {
expect(VM.results).to.be.null
done()
}, 1000)
})
})
I am not sure about moxios mock adaptor, but I had a similar struggle. I ended up using axios, and moxios, with the vue-webpack template. My goal was to fake retreiving some blog posts, and assert they were assigned to a this.posts variable.
Your getData() method should return the axios promise like you said you tried - that way, we have some way to tell the test method the promise finished. Otherwise it will just keep going.
Then inside the success callback of getData(), you can assign your data. So it will look like
return axios.get('url').then((response) {
this.results = response
})
Now in your test something like
it('returns the api call', (done) => {
const vm = Vue.extend(VideoCard)
const videoCard = new vm()
videoCard.getData().then(() => {
// expect, assert, whatever
}).then(done, done)
)}
note the use of done(). That is just a guide, you will have to modify it depending on what you are doing exactly. Let me know if you need some more details. I recommend using moxios to mock axios calls.
Here is a good article about testing api calls that helped me.
https://wietse.loves.engineering/testing-promises-with-mocha-90df8b7d2e35#.yzcfju3qv
So massive kudos to xenetics post above, who helped in pointing me in the right direction.
In short, I was trying to access the data incorrectly, when I should have been using the $data property
I also dropped axios-mock-adaptor and went back to using moxios.
I did indeed have to return the promise in my component, like so;
getData: function () {
let self = this
return axios.get(this.API)
.then(function (response) {
self.results = response.data.data
})
.catch(function (error) {
self.results = error
})
}
(Using let self = this got around the axios scope "problem")
Then to test this, all I had to do was stub the request (after doing the moxios.install() and moxios.uninstall for the beforeEach() and afterEach() respectively.
it('should make the request and update the results variable', (done) => {
moxios.stubRequest('./static/data.json', {
status: 200,
responseText: {
data: [
{ id: 1, name: 'Mexican keyboard cat' },
{ id: 2, name: 'Will it blend?' }
]
}
})
const VM = new Vue(App)
expect(VM.$data.results).to.be.null
VM.getData().then(() => {
expect(VM.$data.results).to.be.an('array')
expect(VM.$data.results).to.have.length(2)
}).then(done, done)
})

Promise isn't working in react component when testing component using jest

Good day. I have the following problem:
I have an item editor.
How it works: I push 'Add' button, fill some information, click 'Save' button.
_onSaveClicked function in my react component handles click event and call function from service, which sends params from edit form to server and return promise.
_onSaveClicked implements
.then(response => {
console.log('I\'m in then() block.');
console.log('response', response.data);
})
function and waits for promise result. It works in real situation.
I created fake service and placed it instead of real service.
Service's function contains:
return Promise.resolve({data: 'test response'});
As you can see fake service return resolved promise and .then() block should work immediatly. But .then() block never works.
Jest test:
jest.autoMockOff();
const React = require('react');
const ReactDOM = require('react-dom');
const TestUtils = require('react-addons-test-utils');
const expect = require('expect');
const TestService = require('./service/TestService ').default;
let testService = new TestService ();
describe('TestComponent', () => {
it('correct test component', () => {
//... some initial code here
let saveButton = TestUtils.findRenderedDOMComponentWithClass(editForm, 'btn-primary');
TestUtils.Simulate.click(saveButton);
// here I should see response in my console, but I don't
});
});
React component save function:
_onSaveClicked = (data) => {
this.context.testService.saveData(data)
.then(response => {
console.log('I\'m in then() block.');
console.log('response', response.data);
});
};
Service:
export default class TestService {
saveData = (data) => {
console.log('I\'m in services saveData function');
return Promise.resolve({data: data});
};
}
I see only "I'm in services saveData function" in my console.
How to make it works? I need to immitate server response.
Thank you for your time.
You can wrap your testing component in another one like:
class ContextInitContainer extends React.Component {
static childContextTypes = {
testService: React.PropTypes.object
};
getChildContext = () => {
return {
testService: {
saveData: (data) => {
return {
then: function(callback) {
return callback({
// here should be your response body object
})
}
}
}
}
};
};
render() {
return this.props.children;
}
}
then:
<ContextInitContainer>
<YourTestingComponent />
</ContextInitContainer>
So your promise will be executed immediately.