I have my app with some screens. One of these screens is called "Race". This race produce a ListView with current race ranking (came from Redux state). This same screen has a TCP connection component which connect to my sensor and get data (async).
When user click on rank item, a new screen is opened with current lap's from clicked racer (item on ListView). At this point, previous screen still working (which is fine), because I can see in my log when data is received by TCP component.
The problem is when user hit back buttom (or navigate to "Race" using side menu), the screen itself is re-created and my TCP component is re-created, which I don't want. So my question is: how can I prevent this screen to be re-constructed OR make this TCP component works like a singleton globally? I'm not sure if is possible or how to make it work with Redux.
Update 1: This is part of code. In this Race screen, I have this internal function that connect my TCP socket.
_connectSensor() {
console.log("Running connectSensor function...");
const { lap_info, dispatchAddLap, dispatchSetSensorStatus } = this.props;
const { race, sensor } = this.props;
if (sensor.sensor_status == SENSOR_ONLINE) {
console.log("Already connected!");
return;
}
dispatchSetSensorStatus(SENSOR_CONNECTING);
//var serverHost = sensor.host;
//var serverPort = sensor.port;
var serverHost = "mydomain.com";
var serverPort = 1212;
console.log("Sensor host: ",serverHost);
console.log("Sensor port: ",serverPort);
this.client = net.createConnection(serverPort, serverHost, () => {
// client.write('Hello, server! Love, Client.');
dispatchSetSensorStatus(SENSOR_ONLINE);
});
this.client.on('data', (data) => {
var obj = JSON.parse(data);
dispatchAddLap(obj);
});
This function is called by a button and it's working fine. When I go to another screen, this TCP socket still running and feeding data to my Redux, which is the desirable state. But when I got back to this screen (from another one), I can't access this socket anymore.....they still running and I can't stop. When I click "Start", "this.client" is a new object because react-navigation re-create my entire screen (my guess....).
I could put some code to force disconnect when this screen is ummounted....but is not what I need. I need to keep socket receiveing data (if user doesn't stop), even if this screen is not active.
Update 2: I have tried to add static client = null; to my class object, but doesn't work.
In my "Stop" button, I've added to debug console.log("Client object:",this.client); and this is the result:
1) When I open race screen and click 'start', then 'stop', object is returned for 'client'.
2) When I open race screen, click start, goe to another screen, go back to race screen and click "Stop": undefined is returned for 'client' object.
Solved. The 'key' for this problem is to export my class as new object, and not just class.
But I had another problem: if I need to use 'connect' from 'react-redux', this doesn't work....simple because 'connect' always return a new object, not the same.
So I made my own ocmponent and create functions to assign dispatchFunctions coming from parent object. This is my final TCP componente:
import React, { Component } from "react";
import { SENSOR_ONLINE, SENSOR_OFFLINE, SENSOR_CONNECTING } from "../constants";
var net = require("react-native-tcp");
class tcpCon extends Component {
static myId;
static dispatchAddLap;
static dispatchSetSensorStatus;
static client;
static sensor_status = SENSOR_OFFLINE;
constructor(props) {
super(props);
console.log("Componente TCP criado! Dados passados: ", props);
var RandomNumber = Math.floor(Math.random() * 10000 + 1);
this.myId = RandomNumber;
}
setDispatchAddLapFunc(func) {
this.dispatchAddLap = func;
}
setDispatchSetSensorStatusFunc(func) {
this.dispatchSetSensorStatus = func;
}
disconnectSensor() {
console.log("Disconnecting....Status no componente TCP: ", this.sensor_status);
if (this.sensor_status == SENSOR_ONLINE) {
this.sensor_status = SENSOR_OFFLINE;
// Dispatch sensor offline
if (this.client) {
console.log("Client object exists. Need to destroy it....");
this.client.destroy();
this.client = null;
} else {
console.log("Client doesn't exists");
}
}
}
displayObj() {
console.log("Client nesta conexao:",this.client);
}
connectSensor(hostname, port) {
console.log("Running connectSensor function...myId: ", this.myId);
console.log("Meu status local:", this.sensor_status);
if (this.sensor_status == SENSOR_ONLINE || this.client) {
console.log("Connection already exists! Returning....");
return;
}
//var con = net.createConnection(port, hostname, () => {
this.client = net.createConnection(port, hostname, () => {
// client.write('Hello, server! Love, Client.');
// dispatchSetSensorStatus(SENSOR_ONLINE);
//this.client = con;
this.sensor_status = SENSOR_ONLINE;
this.dispatchSetSensorStatus(SENSOR_ONLINE);
});
this.client.on("data", data => {
var obj = JSON.parse(data);
//console.log("Data arrived: ",obj);
this.dispatchAddLap(obj);
});
this.client.on("error", error => {
console.log("Erro conectando ao sensor: " + error);
// dispatchSetSensorStatus(SENSOR_OFFLINE);
this.sensor_status = SENSOR_OFFLINE;
this.dispatchSetSensorStatus(SENSOR_OFFLINE);
});
this.client.on("close", () => {
console.log("Conexão fechada no componente TCP.");
// dispatchSetSensorStatus(SENSOR_OFFLINE);
this.sensor_status = SENSOR_OFFLINE;
this.dispatchSetSensorStatus(SENSOR_OFFLINE);
});
//console.log("Client object na connection:", this.client);
//this.dispatchAddLap('Hello world!ID '+this.myId.toString());
}
}
export default new tcpCon();
And this is how I call to connect:
Con.setDispatchAddLapFunc(dispatchAddLap);
Con.setDispatchSetSensorStatusFunc(dispatchSetSensorStatus)
Con.connectSensor("address",1212);
To disconnect, just use this:
Con.setDispatchAddLapFunc(dispatchAddLap); // Just to be sure
Con.setDispatchSetSensorStatusFunc(dispatchSetSensorStatus); // Just to be sure
Con.disconnectSensor();
With this, anytime that I call this component, the same object is returned.
Related
I have added session storage in serve.js as follows :-
import SessionHandler from "./SessionHandler";
const sessionStorage = new SessionHandler();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
API_VERSION: ApiVersion.October21,
IS_EMBEDDED_APP: false,
// This should be replaced with your preferred storage strategy
//SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
sessionStorage.storeCallback,
sessionStorage.loadCallback,
sessionStorage.deleteCallback
),
});
My router get function is
router.get("(.*)", async (ctx) => {
const shop = ctx.query.shop;
let documentQuery = { shop: shop };
let data = await SessionStorage.findOne(documentQuery); //this finds the store in the session table
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
if (data == null) {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
} else {
await handleRequest(ctx);
}
});
and than in the SessionHandler file added code as attached in file ,
but when I run install the app it goes to the storeCallback , loadcallback and deletecallback function multiple times
StoreCallback Function Code
Load and delete callback function code
sorry I have edited my answer as I think its incorrect . all I can say for now is to look at this example:https://github.com/Shopify/shopify-api-node/blob/main/docs/usage/customsessions.md
if you havent already..
I have this weird vuejs effect where when I am adding a new object data, the v-for re-renders all the object even if its already rendered.
I am implementing an infinite scroll like facebook.
The Code
To explain this code, I am fetching a new data from firebase and then push the data into the data object when it reaches the bottom of the screen
var vueApp = new Vue({
el: '#root',
data: {
postList: [],
isOkaytoLoad: false,
isRoomPostEmpty: false,
},
mounted: function() {
// Everytime user scroll, call handleScroll() method
window.addEventListener('scroll', this.handleScroll);
},
methods: {
handleScroll: function()
{
var d = document.documentElement;
var offset = d.scrollTop + window.innerHeight;
var height = d.offsetHeight - 200;
// If the user is near the bottom and it's okay to load new data, get new data from firebase
if (this.isOkaytoLoad && offset >= height)
{
this.isOkaytoLoad = false;
(async()=>{
const lastPost = this.postList[this.postList.length - 1];
const room_id = PARAMETERS.get('room');
const q = query(collection(db, 'posts'), where('room', '==', room_id), orderBy("time", "desc"), limit(5), startAfter(lastPost));
const roomSnapshot = await getDocs(q);
roomSnapshot.forEach((doc) => {
const postID = doc.id;
(async()=>{
// Put the new data at the postList object
this.postList = [...this.postList, doc];
const q = query(collection(db, 'comments'), where('post_id', '==', postID));
const commentSnapshot = await getDocs(q);
doc['commentCount'] = commentSnapshot.size;
//this.postList.push(doc);
console.log(this.postList);
setTimeout(()=>{ this.isOkaytoLoad = true }, 1000);
})();
});
})();
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div v-if="postList.length > 0" class="card-containers">
<!-- I have a component `Postcard` in my js file and it works well -->
<Postcard
v-for="post in postList"
:key="post.id"
:post-id="post.id"
:owner-name="post.data().owner_displayName"
:owner-uid="post.data().owner_id"
:post-type="post.data().post_type"
:image-url="post.data().image_url"
:post-content="truncateString(linkify(post.data().post_content))"
:room="post.data().room"
:time="post.data().time.toDate()"
:likers="post.data().likers"
:comment-count="post.commentCount"
:file-url="post.data().file_url"
:file-name="post.data().file_name"
:downloads="post.data().downloads">
</Postcard>
</div>
Now, the problem is here ...
Look at this screen record, FOCUS AT THE MOUSE, it's lagging and I can't even click on those buttons when vuejs is adding and loading the new data
Here is the code That I used
What I suspect
I am suspecting that everytime I add a new data, the VueJS re-renders it all, which does that effect. How can I force vueJS to not re-render those data that is already rendered in the screen?
You've got two unnecessary async IIFE; the second one inside the forEach is particularly problematic because the async code inside it will be executed concurrently for each loop iteration, which has implications:
getDocs() will be fired all at once for each loop ieration, potentially spamming the server (assuming this is performing a network request). Was this your intention? It looks like you're only fetching at most 5 new posts, so this is probably OK.
The async function updates some state which will trigger Vue to re-render for each doc. This should be batched together at the end so Vue does as minimal updates as possible.
Also don't use var; use const or let instead. There's almost no good reason to use var anymore, let it die.
I can't say this will improve your performance substantially, but I recommend making the following changes (untested):
async handleScroll() {
const d = document.documentElement;
const offset = d.scrollTop + window.innerHeight;
const height = d.offsetHeight - 200;
// If the user is near the bottom and it's okay to load new data, get new data from firebase
if (this.isOkaytoLoad && offset >= height) {
// Prevent loading while we load more posts
this.isOkaytoLoad = false;
try {
// Get new posts
const lastPost = this.postList[this.postList.length - 1];
const room_id = PARAMETERS.get('room');
const q = query(collection(db, 'posts'), where('room', '==', room_id), orderBy("time", "desc"), limit(5), startAfter(lastPost));
const roomSnapshot = await getDocs(q);
// Fetch comments of each post. Do this all at once for each post.
// TODO: This can probably be optimized into a single query
// for all the posts, instead of one query per post.
await Promise.all(roomSnapshot.docs.map(async doc => {
const postID = doc.id;
const q = query(collection(db, 'comments'), where('post_id', '==', postID));
const commentSnapshot = await getDocs(q);
doc.commentCount = commentSnapshot.size;
}));
// Append the new posts to the list
this.postList.push(...roomSnapshot.docs);
} catch (ex) {
// TODO: Handle error
} finally {
// Wait a bit to re-enable loading
setTimeout(() => { this.isOkaytoLoad = true }, 1000);
}
}
}
Doing :post-content="truncateString(linkify(post.data().post_content))" in the template means linkify will be executed during each re-render. I suspect linkify may be potentially slow for long lists? Can this be pre-calculated for each post ahead of time?
You're registering a window scroll event listener when the component is mounted. If the component is ever destroyed, you need to unregister the event listener otherwise it'll still fire whenever the window is scrolled. This may not be an issue in your case, but for reusable components it must be done.
The following code is called on the click of a button
$scope.someFunction = function () {
$scope.submitting = true; // the button is disabled if submitting is true
var query = { query: { id: $scope.employeeID } };
// this api call inserts a record in a table
httpFactory.patch("/someURL", query).then(function (data) {
$scope.submitting = false;
if (data.error) {
// display error message
}
else {
// display success message
}
$scope.submitting = false;
}, function () {
$scope.submitting = false;
});
};
can duplicate records be inserted from the call above if a user has poor connectivity or if the server is slow and the request is not completed and soon another same request is received?
If so.. could any one please suggest a suitable way to handle this?
We jumped into Vue, and totally loved the idea of a store, separating page state from presentation. We wrote a zippy alert component, that displayed whatever alert string was in store.pageError.
Then we wanted to add confirm(), so we gave our component a ref, and a popConfirm() method that returned a promise, and whenever we wanted to confirm something, we called...
vm.$refs.alertConfirm.confirm("Are you sure?")
.then(function(conf){if(conf) /* do it */ })
...and the component became responsible for managing its own visibility, without anything happening in the store.
This worked so well that soon lots of components started sprouting refs, so they could be called directly as methods. In the latest incarnation, we implemented a tunnel with six steps, with api calls and user actions called in parallel with Promise.all(), and it works great, but the store has taken a big step back, and more and more state is being managed directly in Vue components. These components are no longer dumb representations of store state, but increasingly little functional sequences with state managed internally.
How do we reassert the idea of a store, while keeping the convenience of calling these short functional sequences as methods?
Here is our tunnel code. This currently lives in the methods of a Vue component, where markup, state, and sequential logic are joyously mixed. This can't be good?
startTunnel(idNote) {
var rslt = {
idNote: idNote,
photoJustif: null,
date: null,
currency: "",
montant: null
}
//---------------Step 1: photo et count notes in parallel
Promise.all([
me.$refs.camera.click(),
getUrlAsJson("api/note/getNotes"),
])
//---------------Step 2: Choose note if > 1
.then(function (results) {
rslt.photoJustif = results[0];
me.$refs.loader.hide();
// if we already know the note, go straight to Step 3.
if (rslt.idNote)
return true;
// if no idNote supplied, and only one returned from server, assign it.
if (results[1].notes.length === 1 && !rslt.idNote) {
rslt.idNote = results[1].notes[0].idNote;
return true;
}
else {
return me.$refs.chooseNote.choose(results[1].notes)
// combine photoJustif from Step 1 with idNote chosen just above.
.then(function (idNoteChosen) { rslt.idNote = idNoteChosen; return true })
}
})
//--------------Step 3: OCR
.then(() => me.doOcr(rslt))
//--------------Step 4: Choose nature and retrieve card statement from server in parallel
.then(function (ocrResult) {
me.$refs.loader.hide()
if (ocrResult != null) { //Si ocr n'a pas échoué
rslt.date = ocrResult.date;
rslt.montant = ocrResult.montant;
rslt.currency = ocrResult.currency;
return Promise.all([
me.$refs.chooseNature.init(rslt.idNote, ocrResult.grpNatures),
getUrlAsJson("api/expense/relevecarte/filterpers", { IdPerson: 1, montant: ocrResult.montant })
]);
}
else return null;
})
//--------------Step 5: Choose card transaction
.then(function (natureAndFraisCartes) {
if (natureAndFraisCartes != null) {
rslt.idNature = natureAndFraisCartes[0].id;
if (rslt.montant != null && natureAndFraisCartes[1].length > 1)
return me.$refs.choixFraisCarte.init(rslt, natureAndFraisCartes[1]);
else
return null;
}
else return null;
})
//------------- Step 6: End tunnel
.then(function (fraisCarte) {
me.$refs.loader.popInstant();
me.$refs.form.idNote.value = rslt.idNote;
var jsonObject;
if (fraisCarte != null) {
me.$refs.form.action.value = 15;
jsonObject = {
"DateFrais": rslt.date,
"IdNature": rslt.idNature,
"MontantTicket": rslt.montant,
"Justificatif": rslt.photoJustif,
"idCarte": fraisCarte.id
};
}
else {
me.$refs.form.action.value = 14;
jsonObject = {
"DateFrais": rslt.date,
"IdNature": rslt.idNature,
"MontantTicket": rslt.montant,
"Justificatif": rslt.photoJustif,
"idCarte": 0
};
}
me.$refs.form.obj.value = JSON.stringify(jsonObject);
me.$refs.form.submit();
})
.catch(function (error) {
me.$refs.loader.hide();
me.active = false;
me.rslt = {
idNote: idNote,
photoJustif: null,
date: null,
montant: null
};
console.log(error);
vueStore.pageError = me.allStrings.tunnelPhoto.erreurTunnel;
})
}
It looks like the problem is that you got away from thinking declaratively and went to thinking imperatively. When you want to confirm something, you should set a confirmPrompt data item, and the component should be watching it in much the same way it watches the alert string.
There should be a data item for the confirmation response to indicate whether you're waiting for a response, or it was confirmed or it was canceled. It's all program state.
Using $refs is a code smell. It's not always wrong, but you should always think about why you're doing it. Things like me.$refs.loader.hide(); suggest program state changes that should be controlled by setting a loaderIsVisible data item, for example.
Following the method here I'm trying to answer an audio call initiated with a Chrome browser from an iPhone simulator(with React Native).
A summary of the event sequence:
received call signal
got local stream
sent join call signal
received remote description(offer),
created PeerConnection
added local stream
received candidate
added candidate
7 and 8 repeated 15 times (that is 16 times in total)
onnegotiationneeded triggered
signalingState changed into have-remote-offer
onaddstream triggered
the callback function of setRemoteDescription was triggered, created answer.
signalingState changed into stable
iceconnectionstate changed into checking
onicecandidate triggered for the first time.
emited the candidate from 15
onicecandidate triggered for the 2nd time. The candidate is null
iceconnectionstate changed into closed
Step 7,8,9 may appear at different places after 6 and before 19.
I have been stuck on this problem for quite a while. I don't even know what to debug at this time. What are the possible causes of the closing of connection? I can post more logs if needed.
One observation is that the two RTCEvent corresponding to iceconnectionstatechange has the following properties:
isTrusted:false
The target RTCPeerConnection has
iceConnectionState:"closed"
iceGatheringState:"complete"
Here are my functions to handle remoteOffer and remoteCandidates:
WebRTCClass.prototype.onRemoteOffer = function(data) {
var ref;
if (this.active !== true) {
return;
}
var peerConnection = this.getPeerConnection(data.from);
console.log('onRemoteOffer', data,peerConnection.signalingState);
if (peerConnection.iceConnectionState !== 'new') {
return;
}
var onSuccess = (function(_this){
return function(){
console.log("setRemoteDescription onSuccess function");
_this.getLocalUserMedia((function(_this) {
return function(onSuccess,stream) {
peerConnection.addStream(_this.localStream);
var onAnswer = (function(_this) {
return function(answer) {
var onLocalDescription = function() {
return _this.transport.sendDescription({
to: data.from,
type: 'answer',
ts: peerConnection.createdAt,
description: {
sdp: answer.sdp,
type: answer.type
}
});
};
return peerConnection.setLocalDescription(new RTCSessionDescription(answer), onLocalDescription, _this.onError);
};
})(_this);
return peerConnection.createAnswer(onAnswer, _this.onError);
}
})(_this)
);
}
})(this);
return peerConnection.setRemoteDescription(new RTCSessionDescription(data.description),onSuccess,console.warn);
};
WebRTCClass.prototype.onRemoteCandidate = function(data) {
var peerConnection, ref;
if (this.active !== true) {
return;
}
if (data.to !== this.selfId) {
return;
}
console.log('onRemoteCandidate', data);
peerConnection = this.getPeerConnection(data.from);
if ((ref = peerConnection.iceConnectionState) !== "closed" && ref !== "failed" && ref !== "disconnected" && ref !== "completed") {
peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
}
};
I found that if I call the following two functions one by one, then it will work.
peerConnection.setRemoteDescription(new RTCSessionDescription(data.description),onSuccess,console.warn);
(...definition of onAnswer ...)
peerConnection.createAnswer(onAnswer, this.onError);
My previous codes called createAnswer within the onSuccess callback of setRemoteDescription. That did work for the react-native-webrtc demo, but not with Rocket.Chat. Still don't fully understand it. But my project can move on now.