Redux observable - resolving multiple actions - async - reactjs

I'm trying to inject a third party script onto a page and once its loaded initialise a video player.
I want to be able to have multiple video players on any page so I have some logic to check whether the script is already loaded and fire a different actions depending on whether it is or not.
My "script loading" helper looks like this:
/**
* injectScript - Inject player script into page
* #param {String} id - script tag id
* #param {String} src - script src
* #return {Promise} - script load callback
*/
injectScript (id, src) {
return new Promise(resolve => {
if (!document.getElementById(id)) {
const script = document.createElement('script');
script.setAttribute('id', id);
script.type = 'text/javascript';
script.src = src;
document.body.appendChild(script);
script.onload = () => resolve('script-loaded');
} else {
resolve('script-exists');
}
});
}
I then have an epic which calls this helper and fires an action depending on the outcome. The epic looks like this:
/**
* loadPlayerScript
* #param {Object} action$ - action observable
* #param {Object} store - redux store
* #param {Object} dependencies - injected dependencies
* #return {Object} - action observable
*/
export default function loadPlayerScript (action$, store, { scriptLoaderHelper }) {
return action$.ofType(PLAYER_SCRIPT_LOAD)
.switchMap(action => Observable.fromPromise(scriptLoaderHelper.injectScript(action.data.id, action.data.script)))
.map(data => {
if (data === 'script-loaded') {
return playerScriptSuccess();
}
return playerScriptExists();
})
.catch(error => Observable.of(playerScriptFailure(error)));
}
The problem:
The PLAYER_SCRIPT_LOAD gets fired twice (I can validate this in Redux dev tools), this is expected. Whats happening though is the epic is only resolving the promise once (and it only fires one of the actions in the map). How do I get the epic to fire the actions for each promise resolve?
I'm sure its something super simple that I'm overlooking but any helps is appreciated!

Change switchMap to mergeMap to avoid cancellation:
The main difference between switchMap and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable.
source: https://www.learnrxjs.io/operators/transformation/switchmap.html

Related

new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method

I cannot resolve the issue. When application loads react native throw warnings.
WARN `new NativeEventEmitter()` was called with a non-null argument without the required `addListener` method.
WARN `new NativeEventEmitter()` was called with a non-null argument without the required `removeListeners` method.
This is likely due to the newest version of react-native. A lot of libraries still haven't released a new version to handle these warnings (or errors in some case). Examples include https://github.com/react-navigation/react-navigation/issues/9882 and https://github.com/APSL/react-native-keyboard-aware-scroll-view/pull/501.
If it bothers you, you can hide the warning for now (source):
import { LogBox } from 'react-native';
LogBox.ignoreLogs(['new NativeEventEmitter']); // Ignore log notification by message
LogBox.ignoreAllLogs(); //Ignore all log notifications
I just add two function to main java module:
// Required for rn built in EventEmitter Calls.
#ReactMethod
public void addListener(String eventName) {
}
#ReactMethod
public void removeListeners(Integer count) {
}
Example: for fix warning in react-native-fs add functions to android/src/main/java/com/rnfs/RNFSManager.java file.
For Kotlin use this code:
#ReactMethod
fun addListener(type: String?) {
// Keep: Required for RN built in Event Emitter Calls.
}
#ReactMethod
fun removeListeners(type: Int?) {
// Keep: Required for RN built in Event Emitter Calls.
}
same error.
change
const eventEmitter = new NativeEventEmitter(NativeModules.CustomModule);
to
const eventEmitter = new NativeEventEmitter();
works
1- update your react-native-reanimated library to "react-native-reanimated": "2.0.0"
2- You should update the babel.config.json file and add react-native-reanimated/plugin to plugins
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
"react-native-reanimated/plugin",
],
};
This will fix your issue
In my case react-native-location-enabler was creating problem.
just added platform check and executed all the "react-native-location-enabler" specific code just for Android and not for iOS.
This solved the issue.
It is the issue related with react native reanimated library. I solve it by uninstalling the library and reinstalling it. Remove all the installation steps of react-native-reanimated library provided by
https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation.
Simply install library using command
npm install react-native-reanimated#2.3.0-beta.1
If the issue still then open project in android studio. Go to file->invalidate cache. After it all things work right.
The warning is clear. You are passing a param in a constructor that not need it.
Change this:
const eventEmitter = new NativeEventEmitter(CustomModule);
to
const eventEmitter = new NativeEventEmitter();
It's going to works.
Expo users:
Upgraded #config-plugins/react-native-ble-plx to version 5.0.0, and the warning went away.
If you're using #voximplant/react-native-foreground-service, you have to replace node_modules/#voximplant/react-native-foreground-service/index.js by
/*
* Copyright (c) 2011-2019, Zingaya, Inc. All rights reserved.
*/
'use strict';
import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
const isIOS = Platform.OS === 'ios';
const isAndroid = Platform.OS === 'android';
const ForegroundServiceModule = NativeModules.VIForegroundService;
let EventEmitter;
if (isAndroid) {
EventEmitter = new NativeEventEmitter(ForegroundServiceModule);
}
/**
* #property {string} channelId - Notification channel id to display notification
* #property {number} id - Unique notification id
* #property {string} title - Notification title
* #property {string} text - Notification text
* #property {string} icon - Small icon name
* #property {number} [priority] - Priority of this notification. One of:
* 0 - PRIORITY_DEFAULT (by default),
* -1 - PRIORITY_LOW,
* -2 - PRIORITY_MIN,
* 1 - PRIORITY_HIGH,
* 2- PRIORITY_MAX
* #property {string} button - If this property exist, notification will be contain button with text as button value
*/
const NotificationConfig = {
};
/**
* #property {string} id - Unique channel ID
* #property {string} name - Notification channel name
* #property {string} [description] - Notification channel description
* #property {number} [importance] - Notification channel importance. One of:
* 1 - 'min',
* 2 - 'low' (by default),
* 3 - 'default',
* 4 - 'high',
* 5 - 'max'.
* #property {boolean} [enableVibration] - Sets whether notification posted to this channel should vibrate. False by default.
*/
const NotificationChannelConfig = {
};
class VIForegroundService {
static _serviceInstance = null;
_listeners = new Map();
/**
* #private
*/
constructor() {
if (isAndroid) {
EventEmitter.addListener('VIForegroundServiceButtonPressed', this._VIForegroundServiceButtonPressed.bind(this));
}
}
static getInstance() {
if (this._serviceInstance === null) {
this._serviceInstance = new VIForegroundService();
}
return this._serviceInstance;
}
/**
* Create notification channel for foreground service
*
* #param {NotificationChannelConfig} channelConfig - Notification channel configuration
* #return Promise
*/
async createNotificationChannel(channelConfig) {
if (isIOS) {
console.warn("ForegroundService may be used only Android platfrom.")
return;
}
return await ForegroundServiceModule.createNotificationChannel(channelConfig);
}
/**
* Start foreground service
* #param {NotificationConfig} notificationConfig - Notification config
* #return Promise
*/
async startService(notificationConfig) {
if (isIOS) {
console.warn("ForegroundService may be used only Android platfrom.")
return;
}
return await ForegroundServiceModule.startService(notificationConfig);
}
/**
* Stop foreground service
*
* #return Promise
*/
async stopService() {
if (isIOS) {
console.warn("ForegroundService may be used only Android platfrom.")
return;
}
return await ForegroundServiceModule.stopService();
}
/**
* Adds a handler to be invoked when button on notification will be pressed.
* The data arguments emitted will be passed to the handler function.
*
* #param event - Name of the event to listen to
* #param handler - Function to invoke when the specified event is emitted
*/
on(event, handler) {
if (isIOS) {
console.warn("ForegroundService may be used only Android platfrom.")
return;
}
if (!handler || !(handler instanceof Function)) {
console.warn(`ForegroundService: on: handler is not a Function`);
return;
}
if (!this._listeners.has(event)) {
this._listeners.set(event, new Set());
}
this._listeners.get(event)?.add(handler);
}
/**
* Removes the registered `handler` for the specified event.
*
* If `handler` is not provided, this function will remove all registered handlers.
*
* #param event - Name of the event to stop to listen to.
* #param handler - Handler function.
*/
off(event, handler) {
if (isIOS) {
console.warn("ForegroundService may be used only Android platfrom.")
return;
}
if (!this._listeners.has(event)) {
return;
}
if (handler && handler instanceof Function) {
this._listeners.get(event)?.delete(handler);
} else {
this._listeners.set(event, new Set());
}
}
/**
* #private
*/
_emit(event, ...args) {
const handlers = this._listeners.get(event);
if (handlers) {
handlers.forEach((handler) => handler(...args));
} else {
console.log(`[VIForegroundService]: _emit: no handlers for event: ${event}`);
}
}
/**
* #private
*/
_VIForegroundServiceButtonPressed(event) {
this._emit('VIForegroundServiceButtonPressed', event);
}
}
export default VIForegroundService;
Solution found here

Post 404 (Not Found) - Axios

I'm not sure why my post requests are not found?
I'm getting this error
Still getting this error no matter what I have tried and added to my posts im not sure if this is an axios thing or if my routes are actually incorrect please if you have any information about how to fix this or where I have gone wrong please help
xhr.js:177 POST https://enigmatic-cliffs-10818.herokuapp.com/api/user/register 404 (Not Found)
dispatchXhrRequest # xhr.js:177
xhrAdapter # xhr.js:13
dispatchRequest # dispatchRequest.js:52
I need help here is my register api with routes to allow people to register and login and ensure the user is authenticated. I have tried multiple solutions on multiple forums but It doesnt seem to fix or resolve anything
import axios from "axios";
class API {
axios;
constructor() {
this.axios = axios.create();
}
/**
* #param {String} name
* #param {String} value
*/
setHeader( name, value ) {
if( value )
this.axios.defaults.headers.common[name] = value;
else
delete this.axios.defaults.headers.common[name];
}
/**
* #param {object} userData
* #param {String} userData.email
* #param {String} userData.password
*
* #returns {Promise}
*/
register( userData ) {
console.log("------");
console.log(userData);
console.log("------");
return axios.post("/api/user/register", userData);
}
/**
* #param {object} userData
* #param {String} userData.email
* #param {String} userData.password
*
* #returns {Promise}
*/
login( userData ) {
return axios.post("/api/user/login", userData);
}
authenticated() {
return axios.post("/api/user/authenticated");
}
}
export default new API();
I haven't found a solution for my application
The cause for this 404 is in the index.js of the routes directory, when you declare router.use("/api", /* .. */) for the second time, the routes get overridden. This means, the only route associated with the /api endpoint is /addFavorite/:id.
// These are your authentication routes.
router.use("/api", require("./authentication"));
//! These are your other `api` routes.
//! But this line will override the above routes.
//api routes
router.use("/api", apiRoutes);
To solve this, move authentication.js to api directory & make the following changes in /api/index.js.
const router = require("express").Router();
const userRoutes = require("./user");
const authRoutes = require("./authentication");
router.use("/user", userRoutes);
//! Make sure to use the route as `/auth` to avoid collision,
//! since you have defined `/user/login` in the authentication module.
router.use("/auth", authRoutes);
module.exports = router;
By doing so, the authentication routes will be available at /api/auth/user/login.
Hence, you have to make authentication requests to the URL — https://enigmatic-cliffs-10818.herokuapp.com/api/auth/user/register.
fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => console.log(data));
Please use fetch function for your request.

Attempt at invoking a parallel task using Redux-Saga yield all() not working

I'm trying to run two tasks in parallel using yield all() but getting the following log error Object(...) is not a function
When I invoke const response = yield call(request, url) on its own it works. Not sure why yield all() doesn't like the request function I'm passing in.
Here is the code,
const url = `${apiHost}${getSpecificTestConfiguration.endpoint}${configurationId}`;
const [response, itemPool] = yield all([
call(request, url),
put(getItemPool(itemPoolId)),
]);
and here is the request function passed into the call effect where the error is thrown
/**
* Requests a URL, returning a promise
*
* #param {string} url The URL we want to request
* #param {object} [options] The options we want to pass to "fetch"
* #return {object} An object containing either "data" or "err"
*/
export default function request(url, options = {}) {
const opts = addHeaders(options);
return fetch(url, opts)
.then(checkStatus)
.then(parseJSON)
.then((data) => ({ data }));
}
Any help would be greatly appreciated.
Thanks,
My earlier comment was definitely off-base.
Make sure your version of redux-saga is 0.15.0 or higher. This is when all was added.
I was able to reproduce your exact issue in my project using version 0.14.X and the following import: import { ..., all } from 'redux-saga/es/effects, which very helpfully does NOT complain that all doesn't exist. Issue was solved by updating to ^0.15.0 and importing from 'redux-saga/effects'.
Hope this helps.

Firebase function not executed

import * as functions from 'firebase-functions';
console.log('I am a log entry0!');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
console.log('I am a log entry1!');
export const onMessageCreate = functions.database
.ref('/users/{userId}/totalScore')
.onUpdate((change) => {
console.log('I am a log entry2!');
//var a = admin.firestore().collection('/users');
})
I have deployed the function and I can see it in the console. But the function is not executed when totalScore is updated in the database....
Your database is Firestore but you use a Cloud Function that is triggered by an update in the Realtime Database. These are two different Firebase services and you need to change your code accordingly.
The following code will work:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.onMessageCreate = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
// Get an object representing the document
const newValue = change.after.data();
// ...or the previous value before this update
const previousValue = change.before.data();
if (newValue.totalScore !== previousValue.totalScore) {
console.log('NEW TOTAL SCORE');
}
return null;
//I guess you are going to do more than just logging to the console.
//If you do any asynchronous action, you should return the corresponding promise, see point 3 below
//For example:
//if (newValue.totalScore !== previousValue.totalScore) {
// return db.collection("score").doc(newValue.name).set({score: newValue.totalScore});
//}
});
Note that:
You cannot trigger the onUpdate Cloud Function when a specific field of the document changes. The Cloud Function will be triggered when any field of the Firestore document changes. But you can detect which field(s) have changed, as shown in the above code.
Since version 1.0 you have to initialize with admin.initializeApp();, see https://firebase.google.com/docs/functions/beta-v1-diffList
You need to indicate to the platform when the Cloud Function has finished executing: Since you are not executing any asynchronous operation in your Cloud Function you can use return null;. (For more details on this point, I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/).
I think the update is checked on the ref not on the child
Try this
export const onMessageCreate = functions.database
.ref('/users/{userId}')
.onUpdate((change) => {
console.log('I am a log entry2!');
//var a = admin.firestore().collection('/users');
})
You get the old and new values of the snapshot at that location
If you are using Cloud Firestore then your listener is incorrect. In your case, you are specifying a listener for Realtime Database. We extract firestore from the functions and specify the path to the document we want to have a listener on:
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
export const onMessageCreate = functions.firestore
.document('users/{userId}')
.onUpdate((change, context) => {
console.log(change.before.data()); // shows the document before update
console.log(change.after.data()); // shows the document after change
return;
})

Relay Modern: Connecting websocket to network layer

I’m having issues figuring out how to connect the Relay Modern network layer with my websocket instance.
I’m currently instantiating a websocket instance as:
const subscriptionWebSocket = new ReconnectingWebSocket('ws://url.url/ws/subscriptions/', null, options);
I'm specifying the subscription and creating a new instance of requestSubscription:
const subscription = graphql`
subscription mainSubscription {
testData {
anotherNode {
data
}
}
}
`;
requestSubscription(
environment,
{
subscription,
variables: {},
onComplete: () => {...},
onError: (error) => {...},
onNext: (response) => {...},
updater: (updaterStoreConfig) => {...},
},
);
Which then allows me to send any subscription requests:
function subscriptionHandler(subscriptionConfig, variables, cacheConfig, observer) {
subscriptionWebSocket.send(JSON.stringify(subscriptionConfig.text));
return {
dispose: () => {
console.log('subscriptionHandler: Disposing subscription');
},
};
}
const network = Network.create(fetchQuery, subscriptionHandler);
through to my server (currently using Graphene-python), and I’m able to interpret the received message on the server.
However, what I’m having issues figuring out is how to respond to a subscription; for example, when something changes in my DB, I want to generate a response and return to any potential subscribers.
The question being, how do I connect the onMessage event from my websocket instance into my Relay Modern Network Layer? I've browsed through the source for relay but can't seem to figure out what callback, or what method should be implementing an onreceive.
Any tips are appreciated.
I've managed to make subscriptions with Relay Modern work as well and wanted to share my minimal setup, maybe it's helpful for someone!
Note that I'm not using WebSocket but the SubscriptionClient that can be found in subscriptions-transport-ws to manage the connection to the server.
Here's my minimal setup code:
Environment.js
import { SubscriptionClient } from 'subscriptions-transport-ws'
const {
Environment,
Network,
RecordSource,
Store,
} = require('relay-runtime')
const store = new Store(new RecordSource())
const fetchQuery = (operation, variables) => {
return fetch('https://api.graph.cool/relay/v1/__GRAPHCOOL_PROJECT_ID__', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then(response => {
return response.json()
})
}
const websocketURL = 'wss://subscriptions.graph.cool/v1/__GRAPHCOOL_PROJECT_ID__'
function setupSubscription(
config,
variables,
cacheConfig,
observer,
) {
const query = config.text
const subscriptionClient = new SubscriptionClient(websocketURL, {reconnect: true})
const id = subscriptionClient.subscribe({query, variables}, (error, result) => {
observer.onNext({data: result})
})
}
const network = Network.create(fetchQuery, setupSubscription)
const environment = new Environment({
network,
store,
})
export default environment
NewLinkSubscription.js
import {
graphql,
requestSubscription
} from 'react-relay'
import environment from '../Environment'
const newLinkSubscription = graphql`
subscription NewLinkSubscription {
Link {
mutation
node {
id
description
url
createdAt
postedBy {
id
name
}
}
}
}
`
export default (onNext, onError, onCompleted, updater) => {
const subscriptionConfig = {
subscription: newLinkSubscription,
variables: {},
onError,
onNext,
onCompleted,
updater
}
requestSubscription(
environment,
subscriptionConfig
)
}
Now you can simply use the exported function to subscribe. For example, in one of my React components in componentDidMount I can now do the following:
componentDidMount() {
NewLinkSubscription(
response => console.log(`Received data: `, response),
error => console.log(`An error occurred:`, error),
() => console.log(`Completed`)
)
}
Note that the SubscriptionClient can only be used if your server implements this protocol!
If you want to learn more, check out the fullstack How to GraphQL tutorial that describes in detail how to make subscriptions work with Relay Modern.
I’ll just write down how I’ve approached this issue after the assistance found in this thread. It might be usable for someone else. This is very dependent on the server-side solution that you've chosen.
My approach:
Firstly I built a SubscriptionHandler that will handle the requestStream#subscribeFunction through SubscriptionHandler#setupSubscription.
The SubscriptionHandler instantiates a WebSocket (using a custom version of ReconnectingWebSockets) and attaches the onmessage event to an internal method (SubscriptionHandler#receiveSubscriptionPayload) which will add the payload to the corresponding request.
We create new subscriptions through SubscriptionHandler#newSubscription which will use the internal attribute SubscriptionHandler.subscriptions to add a keyed entry of this subscription (we use an MD5-hash util over the query and variables); meaning the object will come out as:
SubscriptionHandler.subscriptions = {
[md5hash]: {
query: QueryObject,
variables: SubscriptionVariables,
observer: Observer (contains OnNext method)
}
Whenever the server sends a subscription response the SubscriptionHandler#receiveSubscriptionPayload method will be called and it will identify what subscription the payload belongs to by using the query/variables md5 hash, then use the SubscriptionHandler.subscriptions observer onNext method.
This approach requires the server to return a message such as:
export type ServerResponseMessageParsed = {
payload: QueryPayload,
request: {
query: string,
variables: Object,
}
}
I do not know if this is a great way of handling subscriptions, but it works for now with my current setup.
SubscriptionHandler.js
class SubscriptionHandler {
subscriptions: Object;
subscriptionEnvironment: RelayModernEnvironment;
websocket: Object;
/**
* The SubscriptionHandler constructor. Will setup a new websocket and bind
* it to internal method to handle receving messages from the ws server.
*
* #param {string} websocketUrl - The WebSocket URL to listen to.
* #param {Object} webSocketSettings - The options object.
* See ReconnectingWebSocket.
*/
constructor(websocketUrl: string, webSocketSettings: WebSocketSettings) {
// All subscription hashes and objects will be stored in the
// this.subscriptions attribute on the subscription handler.
this.subscriptions = {};
// Store the given environment internally to be reused when registering new
// subscriptions. This is required as per the requestRelaySubscription spec
// (method requestSubscription).
this.subscriptionEnvironment = null;
// Create a new WebSocket instance to be able to receive messages on the
// given URL. Always opt for default protocol for the RWS, second arg.
this.websocket = new ReconnectingWebSocket(
websocketUrl,
null, // Protocol.
webSocketSettings,
);
// Bind an internal method to handle incoming messages from the websocket.
this.websocket.onmessage = this.receiveSubscriptionPayload;
}
/**
* Method to attach the Relay Environment to the subscription handler.
* This is required as the Network needs to be instantiated with the
* SubscriptionHandler's methods, and the Environment needs the Network Layer.
*
* #param {Object} environment - The apps environment.
*/
attachEnvironment = (environment: RelayModernEnvironment) => {
this.subscriptionEnvironment = environment;
}
/**
* Generates a hash from a given query and variable pair. The method
* used is a recreatable MD5 hash, which is used as a 'key' for the given
* subscription. Using the MD5 hash we can identify what subscription is valid
* based on the query/variable given from the server.
*
* #param {string} query - A string representation of the subscription.
* #param {Object} variables - An object containing all variables used
* in the query.
* #return {string} The MD5 hash of the query and variables.
*/
getHash = (query: string, variables: HashVariables) => {
const queryString = query.replace(/\s+/gm, '');
const variablesString = JSON.stringify(variables);
const hash = md5(queryString + variablesString).toString();
return hash;
}
/**
* Method to be bound to the class websocket instance. The method will be
* called each time the WebSocket receives a message on the subscribed URL
* (see this.websocket options).
*
* #param {string} message - The message received from the websocket.
*/
receiveSubscriptionPayload = (message: ServerResponseMessage) => {
const response: ServerResponseMessageParsed = JSON.parse(message.data);
const { query, variables } = response.request;
const hash = this.getHash(query, variables);
// Fetch the subscription instance from the subscription handlers stored
// subscriptions.
const subscription = this.subscriptions[hash];
if (subscription) {
// Execute the onNext method with the received payload after validating
// that the received hash is currently stored. If a diff occurs, meaning
// no hash is stored for the received response, ignore the execution.
subscription.observer.onNext(response.payload);
} else {
console.warn(Received payload for unregistered hash: ${hash});
}
}
/**
* Method to generate new subscriptions that will be bound to the
* SubscriptionHandler's environment and will be stored internally in the
* instantiated handler object.
*
* #param {string} subscriptionQuery - The query to subscribe to. Needs to
* be a validated subscription type.
* #param {Object} variables - The variables for the passed query.
* #param {Object} configs - A subscription configuration. If
* override is required.
*/
newSubscription = (
subscriptionQuery: GraphQLTaggedNode,
variables: Variables,
configs: GraphQLSubscriptionConfig,
) => {
const config = configs || DEFAULT_CONFIG;
requestSubscription(
this.subscriptionEnvironment,
{
subscription: subscriptionQuery,
variables: {},
...config,
},
);
}
setupSubscription = (
config: ConcreteBatch,
variables: Variables,
cacheConfig: ?CacheConfig,
observer: Observer,
) => {
const query = config.text;
// Get the hash from the given subscriptionQuery and variables. Used to
// identify this specific subscription.
const hash = this.getHash(query, variables);
// Store the newly created subscription request internally to be re-used
// upon message receival or local data updates.
this.subscriptions[hash] = { query, variables };
const subscription = this.subscriptions[hash];
subscription.observer = observer;
// Temp fix to avoid WS Connection state.
setTimeout(() => {
this.websocket.send(JSON.stringify({ query, variables }));
}, 100);
}
}
const subscriptionHandler = new SubscriptionHandler(WS_URL, WS_OPTIONS);
export default subscriptionHandler;
For anyone stumbling across this recently, I did not have success with either the solutions above because of recent updates in the libraries involved. Yet they were a great source to start and I put up together a small example based on the official relay modern todo example, it is very minimalistic and uses helpers libraries from Apollo but works well with relay modern:
https://github.com/jeremy-colin/relay-examples-subscription
It includes both server and client
Hope it can help
I think this repo would fit your needs.
Helps you creating your subscriptions server-side

Resources