React makes multiple websocket connections to AWS without closing out old sockets - reactjs

Would anyone have any clue what is going on here:
I am trying to create a websocket connection for my company, via API GateWay on AWS. when I use wscat in terminal to connect to my socket it all works fine. It does however, say there was an internal error, but it would still works when I call actions
I have this code in my react code base:
useEffect(() => {
socket.current = new WebSocket(url);
socket.current.onopen = () => {
console.log("ws opened");
socket.current.send(
JSON.stringify({
action: "handleUpdateUserEmail",
email: "test#123.com",
})
);
};
socket.current.onclose = () => console.log("ws closed");
return () => {
socket.current.close();
};
}, []);
I have my dynamoDB database setup to initiate a record in a table to store the current session id, however, I am getting a blank record + another record with the email updates like this code is supposed to do. But it is not closing out old sessions, if I refresh my browser a few times it doesn't close the connections and delete them from the database. Using Wscat it works as intended tho. This is really throwing me in a loop, and I can barely find much online about. The things I am finding online so to do this. Idk what im missing here. I would really appreciate some help

Related

Specifically, how does Reactjs retrieve data from firebase function triggers?

I am using express to create my firebase functions, and I understand how to create regular callable functions. I am lost however on the exact way to implement trigger functions for the background (i.e. onCreate, onDelete, onUpdate, onWrite), as well as how Reactjs in the frontend is supposed to receive the data.
The scenario I have is a generic chat system that uses react, firebase functions with express and realtime database. I am generally confused on the process of using triggers for when someone sends a message, to update another user's frontend data.
I have had a hard time finding a tutorial or documentation on the combination of these questions. Any links or a basic programmatic examples of the life cycle would be wonderful.
The parts I do understand is the way to write a trigger function:
exports.makeUppercase = functions.database.ref('/messages/{pushId}/original')
.onWrite((change, context) => {
// Only edit data when it is first created.
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
// Grab the current value of what was written to the Realtime Database.
const original = change.after.val();
console.log('Uppercasing', context.params.pushId, original);
const uppercase = original.toUpperCase();
// You must return a Promise when performing asynchronous tasks inside a Functions such as
// writing to the Firebase Realtime Database.
// Setting an "uppercase" sibling in the Realtime Database returns a Promise.
return change.after.ref.parent.child('uppercase').set(uppercase);
});
But I don't understand how this is being called or how the data from this reaches frontend code.
Background functions cannot return anything to client. They run after a certain event i.e. onWrite() in this case. If you want to update data at /messages/{pushId}/original to other users then you'll have to use Firebase Client SDK to listen to that path:
import { getDatabase, ref, onValue} from "firebase/database";
const db = getDatabase();
const msgRef = ref(db, `/messages/${pushId}/original`);
onValue(msgRef, (snapshot) => {
const data = snapshot.val();
console.log(data)
});
You can also listen to /messages/${pushId} with onChildAdded() to get notified about any new node under that path.

Handling OAuth with React 18 useEffect hook running twice

Background
I have recently upgraded a fairly sizeable React app to React 18 and for the most part it has been great. One of the key changes is the new double mount in development causing useEffect hooks to all run twice, this is clearly documented in their docs.
I have read their new effect documentation https://beta.reactjs.org/learn/lifecycle-of-reactive-effects and although it is quite detailed there is a use case I believe I have found which is not very well covered.
The issue
Essentially the issue I have run into is I am implementing OAuth integration with a third-party product. The flow:
-> User clicks create integration -> Redirect to product login -> Gets redirected back to our app with authorisation code -> We hit our API to finalise the integration (HTTP POST request)
The problem comes now that the useEffect hook runs twice it means that we would hit this last POST request twice, first one would succeed and the second would fail because the integration is already setup.
This is not potentially a major issue but the user would see an error message even though the request worked and just feels like a bad pattern.
Considered solutions
Refactoring to use a button
I could potentially get the user to click a button on the redirect URL after they have logged into the third-party product. This would work and seems to be what the React guides recommend (Although different use case they suggested - https://beta.reactjs.org/learn/you-might-not-need-an-effect#sharing-logic-between-event-handlers).
The problem with this is that the user has already clicked a button to create the integration so it feels like a worse user experience.
Ignore the duplicate API call
This issue is only a problem in development however it is still a bit annoying and feels like an issue I want to explore further
Code setup
I have simplified the code for this example but hopefully this gives a rough idea of how the intended code is meant to function.
const IntegrationRedirect: React.FC = () => {
const navigate = useNavigate();
const organisationIntegrationsService = useOrganisationIntegrationsService();
// Make call on the mount of this component
useEffect(() => {
// Call the method
handleCreateIntegration();
}, []);
const handleCreateIntegration = async (): Promise<void> => {
// Setup request
const request: ICreateIntegration = {
authorisationCode: ''
};
try {
// Make service call
const setupIntegrationResponse = await organisationIntegrationsService.createIntegration(request);
// Handle error
if (setupIntegrationResponse.data.errors) {
throw 'Failed to setup integrations';
}
// Navigate away on success
routes.organisation.integrations.navigate(navigate);
}
catch (error) {
// Handle error
}
};
return ();
};
What I am after
I am after suggestions based on the React 18 changes that would handle this situation, I feel that although this is a little specific/niche it is still a viable use case. It would be good to have a clean way to handle this as OAuth integration is quite a common flow for integration between products.
You can use the useRef() together with useEffect() for a workaround
const effectRan = useRef(false)
useEffect(() => {
if (effectRan.current === false) {
// do the async data fetch here
handleCreateIntegration();
}
//cleanup function
return () => {
effectRan.current = true // this will be set to true on the initial unmount
}
}, []);
This is a workaround suggested by Dave Gray on his youtube channel https://www.youtube.com/watch?v=81faZzp18NM

Automated testing of discord.js bots

I've been working on creating discord bots using discord.js, and they've gotten large enough that I'd like to be able to set up automated testing for them - I've found this library (corde), but as it doesn't seem to be widely used, I'd like to see if there are other, more mature options available out there first.
Any help would be greatly appreciated. Manually testing bot commands one at a time is growing somewhat tiring.
If you read some of the examples in the github repo, you can simply set the prefix of all of your bots as the same (or change the code below) and then as the client logs in, it tests a command.
const { group, test, command, beforeStart, afterAll } = require("corde");
const { client, loginBot } = require("..");
beforeStart(() => {
loginBot();
});
group("main commands", () => {
test("Hello command should return... hello!!", () => {
expect("ping").shouldReturn("Ping?");
});
});
afterAll(() => {
client.destroy();
});
There is far more code that will help in the repo, here is the main index.js file if you need more assistance, or you could ask in a comment below.

A more performance-friendly way to use setInterval() in componentDidMount when fetching data

i am struggling pretty hard here to find the right solution. Currently, I am using setInterval() to "poll" my server and retrieve an array of objects. To fetch the data, I am using axios. Here are the pertinent functions:
componentDidMount(){
this.timer = setInterval(() => [this.getData(), this.getCustData()], 1000);
}
componentWillUnmount(){
this.timer && clearInterval(this.timer);
this.timer = false
}
getData = () => {
axios.get('http://localhost:3001/api/v1/pickup_deliveries')
.then((response) => {
this.setState({
apiData: response.data
})
})
.catch((error)=>{console.log(error);});
}
getCustData = () => {
axios.get('http://localhost:3001/api/v1/customers')
.then((response) => {
this.setState({
custData: response.data
})
})
.catch((error)=>{console.log(error);});
}
The application is running so slow and often times, it will completely hang the server which makes the whole application unusable. Currently the array it's fetching has over 1000+ objects and that number is growing daily. If I fetch the data without polling the server, the feel of my application is night and day. I am not quite sure what the answer is but I do know what I am doing is NOT the right way.
Is this just the nature of mocking "polling" with setInterval() and it is what it is? Or is there a way to fetch data only when state has changed?
If I need to implement SSE or WebSockets, I will go through the hassle but I wanted to see if there was a way to fix my current code for better performance.
Thanks for hearing me out.
On the frontend side, my advice would be to not use setInterval, but use setTimeout instead.
Using setInterval, your app might send another request even if the response for previous request hasn't come back yet (e. g.: it took more than 1 second). Preferably, you should only send another request 1 second after the previous response is received.
componentDidMount() {
getData();
}
getData = () => {
fetch().then(() => {
updateState();
// schedule next request
setTimeout(getData, 1000);
});
}
You should also try to reduce the amount of updates that need to be done on the frontend, for example by reducing the number of the data.
But nevertheless, I think the most important is to rethink the design of your application. Polling huge JSON that is going to only grow bigger is not a scalable design. It's bad for both the server and the client.
If what you are trying to do is to have the client be notified of any changes in the server side, you should look into WebSocket. A simple idea is that the browser should establish a WS connection to the server. Upon any updates to the server, instead of sending down the whole data, the server should only send down the updates to the client. The client would then update its own state.
For example, let's say 2 users are opening the same page, and one user make changes by adding a new Product. Server will receive this request and update the database accordingly. It will also broadcast a message to all open WebSocket connections (except for the one connection that added the Product), containing a simple object like this:
{
"action": "INSERT",
"payload": {
"product": {
"id": 123123,
... // other product data
}
}
}
The other user will use this data to update its own state so it matches the server.

How to system-test real-time react multiplayer game?

I am working on a real-time multiplayer board game built using node and socket.io for the backend and react + redux for the frontend. It's the first time I'm doing a project of this sort, i.e. real-time and multiplayer.
I am unsure on how best to go about integration/system testing. How can I actually automate spinning up, say 10 frontends and having them play a game together? Should I use a testing framework for this and if so, which one would be a good choice and why?
I found this question with the same question. I have figured out a way for it to work, which I'm guessing you have as well by now, but for someone else to come across:
A clarification on terms: integration testing can be done for react with things like Jest + enzyme, using mount(). I'm answering this based on looking for end-to-end / acceptance testing, where you're essentially testing your product from the user's standpoint (here, navigating around on a website).
As this is from the user's perspective, I believe it is irrelevant that you're using React.
So, how to do this? There are many JS testing options. That resource can help understand which testing package you might want to select. You want something that simulates an actual browser.
In investigating some of the options listed in the above resource, I have found that:
TestCafe does not support multi-page testing.
WebdriverIO does support multiple browsers at the same time.
Puppeteer does support multiple pages at the same time.
edit: I initially proposed using nightmare. However, I was getting some wonky behavior when running multiple tests (unexpected timeouts, Electron instances not closing properly), and have investigated some other options. But I'll retain the information for reference:
I selected nightmare because it was advertised as simple.
Below is an example test, using Jest and nightmare (and some sloppy TypeScript). The site has a button to end the player's turn, and there is a header that indicates whose turn it is. I'll simulate clicking that button and making sure the header changes as expected. Also note that you'll need your dev server and frontend running during these tests.
import * as Nightmare from 'nightmare';
let nightmare1: Nightmare;
let nightmare2: Nightmare;
beforeEach(async () => {
nightmare1 = new Nightmare({ show: true })
nightmare2 = new Nightmare({ show: true })
await nightmare1
.goto('http://127.0.0.1:3000');
await nightmare2
.goto('http://127.0.0.1:3000');
});
afterEach(async () => {
await nightmare1.end();
await nightmare2.end();
});
it('sockets turn changes via End Turn button', async () => {
expect.assertions(6);
// Both display the same player's turn ("Red's Turn")
const startingTurnIndicator1 = await nightmare1
.evaluate(() => document.querySelector('h1').innerText);
const startingTurnIndicator2 = await nightmare2
.evaluate(() => document.querySelector('h1').innerText);
expect(startingTurnIndicator1).toBe(startingTurnIndicator2);
// Both change ("Blue's Turn")
const oneClickTI1 = await nightmare1
.click('button')
.evaluate(() => document.querySelector('h1').innerText)
const oneClickTI2 = await nightmare2
.evaluate(() => document.querySelector('h1').innerText);
expect(oneClickTI1).toBe(oneClickTI2);
expect(oneClickTI1).not.toBe(startingTurnIndicator1);
// Both change back ("Red's Turn")
const twoClickTI2 = await nightmare2
.click('button')
.evaluate(() => document.querySelector('h1').innerText)
const twoClickTI1 = await nightmare1
.evaluate(() => document.querySelector('h1').innerText);
expect(twoClickTI1).toBe(twoClickTI2);
expect(twoClickTI1).toBe(startingTurnIndicator2);
expect(twoClickTI1).not.toBe(oneClickTI1);
});
I'm not sure how good the actual code in this test is, but it works.

Resources