socket.io client receiving multiple calls to same event in react native - node.js

Whenver the user login into the application. he joins to its own userId in server via
socket.join(uid)
whereas the nodejs endpoint looks like
router.post("/secured/postmessage", (req,res)=>{
const { message, receiverId } = req.body;
io.to(receiverId).emit("newMessage", {
msgBody: message,
sender: req.currentUser,
});
})
now the RN part:
Chat screen functional Component looks like
export default function Chat({ navigation }) {
//receiveing the socket as props from previous screen
const { contact, socket } = navigation.state.params;
// console.log("in conversation contact is ", contact);
const [data, setData] = useState([
{
id: 8,
date: "9:50 am",
type: "in",
message: "Lorem ipsum dolor sit a met",
},
]);
//this gets fired multiple times <--------
socket.on("newMessage", ({ msgBody, sender }) => {
setData((oldMessages) => [...oldMessages, msgBody]);
});
//handleSubmit gets fired when user types message and press SEND
const handleSubmit(){
//sending the post request to server with message
axios.post(baseUrl + "/secured/postmessage", {
message: message,
receiverId: contact._id,
})
}
return(
...
)
whereas the nodejs endpoint looks like
router.post("/secured/postmessage", (req,res)=>{
io.to(receiverId).emit("newMessage", {
msgBody: messageResult,
sender: req.currentUser,
});
})
socket.on('newMessage') in Chat screen is getting fired multiple times, I dont know why

I think you can try adding socket event handler only when your Chat component mounted.
In functional component, you can use React.useEffect().
refer to below
React.useEffect(() => {
socket.on("newMessage", ({ msgBody, sender }) => {
setData((oldMessages) => [...oldMessages, msgBody]);
});
},[]);

Related

How to send real time notification with socket.io in express

I want to send a real-time notification to the owner of the post when the post is liked. But, I don't really have an idea how to implement it into my React app and make it work. My notification function on the server side looks like this;
const Notification = require("../models/NotificationModel.js");
const { Server } = require("socket.io");
const io = new Server({
cors: "clientURL",
});
const sendNotification = async ({
sender,
receiver,
message,
project,
comment,
challenge,
}) => {
io.on("connection", (socket) => {
socket.on("sendNotification", ({sender, receiver, message}) => {
io.to(receiver.socketId).emit("getNotification", {
sender,
message
})
});
});
await Notification.create({
message,
sender,
receiver,
project,
comment,
challenge,
});
};
module.exports = sendNotification;
According to the OpenGPT client-side should look like this;
import React, { useEffect } from 'react';
import io from 'socket.io-client';
const socket = io('serverURL');
const MyComponent = () => {
useEffect(() => {
socket.on("getNotification", (data) => {
// Handle the notification with the data from the server
});
}, []);
return (
// Render your component
);
};
Could you please point out all the wrongs in this snippet and provide the correct way to make these actions in sequence;
user likes a post -> server sends a notification to the owner of the post

Push notification shows object Object even though i am setting the right value

I am trying to implement push notifications with react and nodejs using service workers.
I am having problem while i am showing notification to the user.
Here is my service worker code:
self.addEventListener('push', async (event) => {
const {
type,
title,
body,
data: { redirectUrl },
} = event.data.json()
if (type === 'NEW_MESSAGE') {
try {
// Get all opened windows that service worker controls.
event.waitUntil(
self.clients.matchAll().then((clients) => {
// Get windows matching the url of the message's coming address.
const filteredClients = clients.filter((client) => client.url.includes(redirectUrl))
// If user's not on the same window as the message's coming address or if it window exists but it's, hidden send notification.
if (
filteredClients.length === 0 ||
(filteredClients.length > 0 &&
filteredClients.every((client) => client.visibilityState === 'hidden'))
) {
self.registration.showNotification({
title,
options: { body },
})
}
}),
)
} catch (error) {
console.error('Error while fetching clients:', error.message)
}
}
})
self.addEventListener('notificationclick', (event) => {
event.notification.close()
console.log(event)
if (event.action === 'NEW_MESSAGE') {
event.waitUntil(
self.clients.matchAll().then((clients) => {
if (clients.openWindow) {
clients
.openWindow(event.notification.data.redirectUrl)
.then((client) => (client ? client.focus() : null))
}
}),
)
}
})
When new notification comes from backend with a type of 'NEW_MESSAGE', i get the right values out of e.data and try to use them on showNotification function but it seems like something is not working out properly because notification looks like this even though event.data equals to this => type = 'NEW_MESSAGE', title: 'New Message', body: , data: { redirectUrl: }
Here is how notification looks:
Thanks for your help in advance.
The problem was i assigned parameters in the wrong way.
It should've been like this:
self.registration.showNotification(title, { body })

How display payment succeeded without return_url param in Stripe

I have one problem in integration Stripe into my React application. I use code from official Stripe documentation. It works expected. My question is how to check is payment succeeded without using return_url ? Am I required to use return url ? I found in Stripe documentation redirect: "if_required" option, but that doesnt make anything. I just get error problem in my console if I put this object in confirmPayment method. I would like have scenario is payment successfull that client navigate to some Confirmation page and to get message payment successfully.
App.jsx
import { loadStripe } from "#stripe/stripe-js";
import { Elements } from "#stripe/react-stripe-js";
import CheckoutForm from "./CheckoutForm";
import "./App.css";
// Make sure to call loadStripe outside of a component’s render to avoid
// recreating the Stripe object on every render.
// This is your test publishable API key.
const stripePromise = loadStripe("pk_test_51LmE9VAoYs2flpvClDqeh0f1vhaDUkBM0bRGaJgThjtaMd3PiPUGQOHjn9f7XW1HGgSQBvTq3xoLy9PovlWLPUnR0031srjgyb");
export default function App() {
const [clientSecret, setClientSecret] = useState("");
useEffect(() => {
// Create PaymentIntent as soon as the page loads
fetch("/create-payment-intent", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ items: [{ id: "xl-tshirt" }] }),
})
.then((res) => res.json())
.then((data) => setClientSecret(data.clientSecret));
}, []);
const appearance = {
theme: 'stripe',
};
const options = {
clientSecret,
appearance,
};
return (
<div className="App">
{clientSecret && (
<Elements options={options} stripe={stripePromise}>
<CheckoutForm />
</Elements>
)}
</div>
);
}
CheckoutForm.jsx
import {
PaymentElement,
useStripe,
useElements
} from "#stripe/react-stripe-js";
export default function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
if (!stripe) {
return;
}
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
if (!clientSecret) {
return;
}
stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => {
switch (paymentIntent.status) {
case "succeeded":
setMessage("Payment succeeded!");
break;
case "processing":
setMessage("Your payment is processing.");
break;
case "requires_payment_method":
setMessage("Your payment was not successful, please try again.");
break;
default:
setMessage("Something went wrong.");
break;
}
});
}, [stripe]);
const handleSubmit = async (e) => {
e.preventDefault();
if (!stripe || !elements) {
// Stripe.js has not yet loaded.
// Make sure to disable form submission until Stripe.js has loaded.
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
// Make sure to change this to your payment completion page
return_url: "http://localhost:3000",
},
});
// This point will only be reached if there is an immediate error when
// confirming the payment. Otherwise, your customer will be redirected to
// your `return_url`. For some payment methods like iDEAL, your customer will
// be redirected to an intermediate site first to authorize the payment, then
// redirected to the `return_url`.
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message);
} else {
setMessage("An unexpected error occurred.");
}
setIsLoading(false);
};
return (
<form id="payment-form" onSubmit={handleSubmit}>
<PaymentElement id="payment-element" />
<button disabled={isLoading || !stripe || !elements} id="submit">
<span id="button-text">
{isLoading ? <div className="spinner" id="spinner"></div> : "Pay now"}
</span>
</button>
{/* Show any error or success messages */}
{message && <div id="payment-message">{message}</div>}
</form>
);
}
When using redirect: 'if_required', then the return_url attribute becomes not required.
If no redirection is required then you need to wait for the confirmation from the method stripe.confirmPayment and check if there is an error in the response.
To do so, you can adapt your CheckoutForm.jsx file and adapt your function handleSubmit like below:
setIsLoading(true);
const response = await stripe.confirmPayment({
elements,
confirmParams: {
},
redirect: 'if_required'
});
if (response.error) {
showMessage(response.error.message);
} else {
showMessage(`Payment Succeeded: ${response.paymentIntent.id}`);
}
setIsLoading(false);
Also, if you want to get notified from your backend when a successful payment has occurred, you can set a webhook[1] and listen to this Stripe event payment_intent.succeeded[2]
[1] https://stripe.com/docs/webhooks
[2] https://stripe.com/docs/api/events/types#event_types-payment_intent.succeeded

Node.js +Vue App +Websockets, updating list of connected elements

I'm trying to make this app based on node+socket.io+Vue.js.
Referring to the back end I set the server connected to socket io and all referring to the different emitters :
Class Server
const express = require("express");
const cors = require("cors");
const corsObject = {
origin: ["http://localhost:8100"],
methods: ["GET", "POST", "DELETE", "PUT", "PATCH"],
allowedHeaders: ["Content-Type", "Authorization","token-response"],
credentials: true,
};
const { conectToMongo } = require("../database/database");
const {
socketController,
} = require("../sockets-controllers/socket-controller");
class Server {
constructor() {
this.allowedOrigins = "http://localhost:8100";
this.app = express();
this.port = process.env.PORT; arranque
this.connectingdatabase();
this.server = require("http").createServer(this.app);
this.io = require("socket.io")(this.server, {
cors: corsObject,
});
this.connectionSocketClient();
this.middlewares();
this.routes();
}
middlewares() {
this.app.use(express.json()); //lectura y parsoin del body a fomato json
this.app.use(express.static("public")); //directorio fornt
this.app.use(cors({ credentials: true, origin: "http://localhost:8100" })); //cors
this.app.get("/", (request, response, next) => {
response.status(200).json({
ok: true,
message: "request correct",
});
});
}
routes() {
this.app.use("/user", require("../routes/user-routes"));
this.app.use("/auth", require("../routes/user-login"));
// this.app.use(this.usuariosPath,require('../routes/user-routes'))
}
async connectingdatabase() {
await conectToMongo();
}
connectionSocketClient() {
this.io.on("connection",(socket)=> socketController(socket,this.io));
}
portListener() {
this.server.listen(this.port, () => {
console.log(`server running on port : ${this.port}`);
});
}
}
module.exports = Server; //Exportando la clase interface creada para su uso en los demas modulos
In order to initialize the controllers i first set a class which might be used on controllers in a easier way importing its instance
class Message {
constructor(userId, userMessage, message) {
this.userId = userId;
this.userMessage = userMessage;
this.message = message;
}
} //Class and its constructor Message
class ChatMessage {
constructor() {
this.messages = [];
this.usersOnConnection = {};
}
//constructor of this class with two components , first a array of message
//and second a object that would be stacking the connected users
get usersConnected() {
return Object.values(this.usersOnConnection);
}
//This getter would retrieve the object of users connected converting it to
//an array
addUserToChat(user) {
this.usersOnConnection[user.id] = user
}
//this method would add a user to a chat i this case adding the new consumer
//to the object of users connected usersOnConnection(HERE problem too)
}
//Class and its constructor Message
module.exports = { Message, ChatMessage };
Then the socket controller on charge of set the logic would be like this
const { Socket } = require("socket.io");
const { ChatMessage, Message } = require("../models/chat-model");//Interfaces imported
const socketController = async (socket = new Socket(), io) => {
const user = await jwtValidatorRenew(
socket.handshake.headers["token-response"]
);
if (!user || user == undefined) {
console.log("user disconnected");
return socket.disconnect();
}
//if for some reason the user requested thorugh a token i retrieve is null
//or some like , the controller ends here with a disconnection to the socket
//else
const chatMessage =await new ChatMessage(); //creating new isntance of class ChatMessage
//first i do create a new instance of the class Chatmessage in order to access from here
chatMessage.addUserToChat(user); //adding user to the chat (HERE )
//then any time the browser is recharged or user inits session , this user
//would be added, using the method addUserToChat from the class instance
io.emit("active-users", chatMessage.usersConnected); //sending users connected
//once the user is added i do proceed to emit through io , the state of all connected
//consumers aiming to the flag "active-users", accesing the getter usersConnected
//from the instance
socket.on("disconnect", () => {
console.log("Client DisconectedConected", socket.id); // desconexion
chatMessage.disconnectUserFromChat(user.id);
//diconnecting user
io.emit("active-users", chatMessage.usersConnected);
});
//On disconnection the instance of chatmessage class is called in order to disconnect
//user according to its id. Also accession io from socket I do emit for all consumers
//a new state under the flag "active-users", passing ass method the getter usersConnected
//from the instance chatMessage(but only receive one user)
};
module.exports = { socketController };
Then on my front after setting socket io client i set this on my Vue state manangement(vuex ) for the socket connection, being this action dispatched any time i need.
ACTIONS VUEX
conectingSocket() {
const socket= io("localhost:3006", {
extraHeaders: {
"token-response": localStorage.getItem("token"),
},
});
socket.on("connect",()=>{
console.log("socket online");
})
//flag on connection
socket.on("disconnect",()=>{
console.log("socket offline");
})
//flag on discconnection
socket.on("active-users",(usersPayload)=>{
console.log(usersPayload);
})
//Here i log the users connected updated once the back emit on this flag a action . But always
//brings me the user of the browser that I recharge not updating the other ones
},
Then any time i initialize my app this method on front is triggered in order to retrieve the users
connected, thus for that simply call the method and set it also in my mounted Vue life cycle:
export default {
name: "AllUsersComponent",
components: { IonCard, IonContent, IonItem, IonInput, IonButton, IonLabel },
// components: { IonLabel, IonInput, IonItem },
data() {
return {
allUsersFinal: [],
message: "",
state: false,
socket: io(process.env.VUE_APP_BACK_URL),
//client socket and its connection
};
},
methods: {
...mapActions(["getAllUsers", "validateToken", "conectingSocket"]),
socketConection() {
this.$store.dispatch("conectingSocket");
},
...some methods
},
},
computed: {
...mapGetters(["getterGetAllUsers"]),
...some computed methods
},
mounted() {
this.socketConection()
},
created() {
...some methods
},
};
</script>
But keeps showing me only the user of the browser I do recharge. For this purpose i initialize on the app two users from different browsers(Firefox and Chrome)
Any help on this would be amazing!!!!!

React hooks are overwritting object array

I'm trying to code a chatbot interface using React hooks and Wit.ai.
I have tried setting the messages imperatively (setMessages([...messages, currentValue]) but that doesn't work either. Here's the code:
const [currentValue, setCurrentValue] = useState('');
const [messages, setMessages] = useState([]);
const handleChange = (event) => {
setCurrentValue(event.target.value); // handling input change
}
const sendMessage = (event) => {
event.preventDefault();
if (currentValue.trim() !== '' || currentValue.trim().length > 1) {
witClient.message(currentValue).then(data => {
setMessages(() => [...messages, {text: currentValue, sender: 'user'}]); // here i set the user message
if (data.entities) {
setMessages(() => [...messages, {text: 'message from bot', sender: 'bot'}]); // this line seems to overwrite the user message with the bot message
setCurrentValue('');
}
});
}
document.querySelector('input').focus();
}
When I handle the bots response it overwrites the users message.
Since you are relying on prior values you can use functional pattern for setting state like below:
Docs: https://reactjs.org/docs/hooks-reference.html#functional-updates
setMessages((priorMessages) => [...priorMessages, {text: currentValue, sender: 'user'}]);
======================================
if (data.entities) {
setMessages((priorMessages) => [...priorMessages, {text: 'message from bot', sender: 'bot'}]);
When you access messages after the if statement you're actually overwritting the first changes, cause [...messages, {text: currentValue, sender: 'user'}] will only be reflected in the next render. Set your changes all at once to prevent this
const sendMessage = (event) => {
event.preventDefault();
if (currentValue.trim() !== '' || currentValue.trim().length > 1) {
witClient.message(currentValue).then(data => {
let newMessages = [...messages, {text: currentValue, sender: 'user'}]
if (data.entities) {
newMessages = newMessages.concat({text: 'message from bot', sender: 'bot'})
setCurrentValue('');
}
setMessages(messages)
});
}
document.querySelector('input').focus();
}

Resources