Electron webcontents.send does not work in production - reactjs

I have an electron react app where I am trying to communicate between two renderer processes, I managed to get it to work on the dev server but it does not work in production.
Main Process:
ipcMain.on('pass-info', async (event, args: any) => {
console.log('pass-info', args);
const childWindow = createWindow('child', {
width: 1000,
height: 600,
show: false,
});
if (!isProd) {
const prodPath = path.join(__dirname, '../app/test.html');
// await childWindow.loadURL(`file://${prodPath}`);
await childWindow.loadFile(prodPath);
childWindow.show();
} else {
const port = process.argv[2];
await childWindow.loadURL(`http://localhost:${port}/test`);
childWindow.webContents.openDevTools();
childWindow.show();
}
const id = setTimeout(() => {
childWindow.webContents.send('pass-info-response', args);
}, 2000);
return () => clearTimeout(id);
});
I also tried this instead of the setTimeout way
childWindow.webContents.on('did-finish-load', () => {
childWindow.webContents.send('pass-info-response', args);
});
And here is my React page:
const Test = () => {
const [amount, setAmount] = useState('');
React.useEffect(() => {
window.electronAPI.passInfoResponse((event, data) => {
// alert(data);
console.log(data);
setAmount(data.amount);
});
}, []);
return (
<div>
<h1>Test</h1>
{!!amount ? <div>{amount}</div> : <div>Loading...</div>}
</div>
);
};

Related

Getting duplicate messages with React + Socket.IO chat app

Attempting to create a simple React chat app, but I'm running into an issue where duplicate messages appear. I run the server with node server.js, in a separate terminal run the client with npm start, then open two windows at localhost:3000. I can send a message, but any message I send is received x2. For example (after submitting the form with "i only sent this once" on one window, this is what appears in the second window).:
I did console.log on the server-side, and only one message is being received from the server.
Here is my server.js file:
const express = require("express");
const socketio = require("socket.io");
const http = require("http");
const cors = require("cors");
const PORT = process.env.PORT || 5001;
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(cors());
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
io.on("connection", (socket) => {
socket.on("connection", () => {});
socket.on("send-message", (message) => {
console.log(message);
socket.broadcast.emit("message", message);
});
});
Here's my socket.js file:
import io from "socket.io-client";
const SERVER = "http://127.0.0.1:5001";
const connectionOptions = {
forceNew: true,
reconnectionAttempts: "Infinity",
timeout: 10000,
transports: ["websocket"],
};
let socket = io.connect(SERVER, connectionOptions);
export default socket;
Here's my App.js file:
import { useState, useEffect } from "react";
import "./App.css";
import socket from "./socket";
const ChatWindow = () => {
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const handleMessageChange = (event) => {
setMessage(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
socket.emit("send-message", { message: message });
};
useEffect(() => {
socket.on("connection", null);
socket.on("message", (payload) => {
setMessages((prev) => {
return [...prev, payload.message];
});
});
}, []);
return (
<div>
{messages.map((message) => {
return <h3>message: {message}</h3>;
})}
<form onSubmit={handleSubmit}>
<input value={message} onChange={handleMessageChange}></input>
<button type="submit">Send Message</button>
</form>
<h1>Chat Window</h1>
</div>
);
};
function App() {
return (
<div className="App">
<ChatWindow />
</div>
);
}
export default App;
As punjira answered in a comment, removing the event listener for the useEffect worked.
Specifically, before:
useEffect(() => {
socket.on("connection", null);
socket.on("message", (payload) => {
setMessages((prev) => {
return [...prev, payload.message];
});
});
}, []);
After:
useEffect(() => {
socket.on("connection", null);
socket.on("msg", (payload) => {
setMessages((prev) => {
return [...prev, payload.msg];
});
});
return function cleanup() {
socket.removeListener("msg");
};
}, []);
If you are using UseEffects Hooks you need to clean up
Before :
useEffect(() => {
socket.on("connection", null);
socket.on("message", (payload) => {
setMessages((prev) => {
return [...prev, payload.message];
});
});
}, []);
You can add this line to clean up :
useEffect(() => {
socket.on("connection", null);
socket.on("message", (payload) => {
setMessages((prev) => {
return [...prev, payload.message];
});
});
return () => socket.off("message"); // add this line to your code
}, []);

React Context - fix unecessary rerenders - eslint issue

Eslint throw error:
The object passed as the value prop to the Context provider (at line
147) changes every render. To fix this consider wrapping it in a
useMemo hook
I have all my functions in callbacks, and also use a bit of useMemo however I sort of have no clue how to proceed with others to make my context clean and highly performance without unecessary rerenders, Would be grateful if someone could take a look and see how can it be improved to make error disappear and context work fine
const MapProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const [coordinates, setCoordinates] = useState<LatLngType>({ lat: 9.05789, lng: 29.92313 });
const [activeMarker, setActiveMarker] = useState<string>('');
const [directions, setDirection] = useState<DirectionResults>();
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: process.env.REACT_APP_GOOGLE_MAPS ?? ''
});
const mapRef = useRef<MapType>();
const options = useMemo<MapOptionsType>(() => {
return {
disableDefaultUI: false,
clickableIcons: false
};
}, []);
const onLoad = useCallback((map: MapType) => {
mapRef.current = map;
}, []);
const getCurrentPosition = useCallback(async () => {
try {
const coord = await Geolocation.getCurrentPosition({ enableHighAccuracy: true });
setCoordinates({
lat: coord.coords.latitude,
lng: coord.coords.longitude
});
} catch (error) {
console.log(error);
}
}, []);
const watchUserPosition = useCallback(async () => {
const watchOptions = { timeout: 60000 };
const showLocation = (position: Position | null, err?: any): void => {
if (err) return console.error('Map was not loaded', err);
if (position) {
const {
coords: { latitude, longitude }
} = position;
setCoordinates({
lat: latitude,
lng: longitude
});
}
};
await Geolocation.watchPosition(watchOptions, showLocation);
}, []);
useEffect(() => {
getCurrentPosition();
watchUserPosition();
}, []);
const moveToMarkerLocation = useCallback(
(marker_coordinates: LatLngType) => {
mapRef.current?.panTo(marker_coordinates);
},
[mapRef.current]
);
const openMarker = useCallback((marker: string) => {
setActiveMarker(marker);
}, []);
const closeMarker = useCallback(() => {
setActiveMarker('');
}, []);
const showDirection = useCallback(
(marker_position: LatLngType) => {
const service = new window.google.maps.DirectionsService();
service.route(
{
origin: coordinates,
destination: marker_position,
travelMode: window.google.maps.TravelMode.WALKING
},
(result, status) => {
if (status === 'OK' && result) {
setDirection(result);
}
}
);
},
[coordinates]
);
const endDirection = useCallback(() => {
setDirection(undefined);
}, []);
return (
<MapContext.Provider
value={{
isLoaded,
mapRef,
moveToMarkerLocation,
coordinates,
options,
onLoad,
openMarker,
closeMarker,
activeMarker,
directions,
showDirection,
endDirection
}}>
{children}
</MapContext.Provider>
);
};
Way to make error disappear is :
const mapValues = useMemo(
() => ({
isLoaded,
mapRef,
moveToMarkerLocation,
coordinates,
options,
onLoad,
openMarker,
closeMarker,
activeMarker,
directions,
showDirection,
endDirection
}),
[
isLoaded,
mapRef,
moveToMarkerLocation,
coordinates,
options,
onLoad,
openMarker,
closeMarker,
activeMarker,
directions,
showDirection,
endDirection
]
);
return <MapContext.Provider value={mapValues}>{children}</MapContext.Provider>;
But I'm not convinced if it solve the problem with performance eventho eslint error is gone

React - useEffect being called too many times while using it for "scroll to bottom" of messages list

I'm trying to implement "scroll to bottom" function. Here is my code:
const messagesEndRef = useRef();
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
console.log("scroll");
}
};
And I use "useEffect" to trigger it:
useEffect(() => {
scrollToBottom();
}, [messages]);
Here is the place where I implement the view:
<div className="scroll">
<ListMessages listMessages={messages} />
<div ref={messagesEndRef} />
</div>
And here is the result on browser:
You guys can see that the word "scroll" is printed duplicated (or more) after every message. It makes the app very slow
Could you guys have any idea to help me to solve this case?
Thank you in advance!
PS: Here is my full component
const ws = new WebSocket("ws://localhost:8080/chat");
const Chat = () => {
const createWebSocket = () => {
ws.addEventListener("open", () => {
console.log("We are connected!");
});
ws.addEventListener("message", (e) => {
receiveMessage(e.data);
});
};
createWebSocket();
const [messages, setMessages] = useState([]);
const messagesEndRef = useRef();
const pushMessageToList = async (message, sentBy) => {
let inputMessage = {
id: Date.now(),
message: message,
sentBy: sentBy,
};
let listMessages = [...messages];
listMessages.push(inputMessage);
setMessages(listMessages);
};
const receiveMessage = (message) => {
pushMessageToList(message, "chatbot");
};
const sendInputMessage = (message) => {
pushMessageToList(message, "user");
ws.send(message);
};
const scrollToBottom = () => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
console.log("scroll");
}
};
useEffect(() => {
scrollToBottom();
}, [messages]);
useEffect(() => {
// component did mount
}, []);
return (
<div className="bg-white">
<div className="scroll">
<ListMessages listMessages={messages} />
<div ref={messagesEndRef} />
</div>
<Input sendInputMessage={sendInputMessage} />
</div>
);
};
export default Chat;
Your code is adding new listeners to the websocket on every render, that's why you are getting incremental logs.
You have to setup handlers inside a useEffect hook, and remove them in the cleanup function of the hook itself (see docs), like:
useEffect(() => {
function onOpen() {
console.log("We are connected!");
}
function onMessage({ data }) {
setMessages([
...messages,
{
id: Date.now(),
message: data.message,
sentBy: 'chatbot'
}
]);
}
ws.addEventListener("open", onOpen);
ws.addEventListener("message",onMessage);
return () => {
ws.removeEventListener("open", onOpen);
ws.removeEventListener("message", onMessage);
}
}, [messages]);
(This hook needs the messages dependency, because you want to update the messages based on the previous state - I think you can do with the callback as well, without any dependency:
setMessages(prevMessages => ([
...prevMessages,
{
id: Date.now(),
message: data.message,
sentBy: 'chatbot'
}
]));
Now you can use the scroll hook in the same hook if you are listing messages as dependency, or in separate one like you have now in the other case.
Your full component will look something like:
const ws = new WebSocket("ws://localhost:8080/chat");
const Chat = () => {
const [messages, setMessages] = useState([]);
const messagesEndRef = useRef();
useEffect(() => {
function onOpen() {
console.log("We are connected!");
}
function onMessage({ data }) {
setMessages((prevMessages) => [
...prevMessages,
{
id: Date.now(),
message: data.message,
sentBy: "chatbot",
},
]);
}
ws.addEventListener("open", onOpen);
ws.addEventListener("message",onMessage);
return () => {
ws.removeEventListener("open", onOpen);
ws.removeEventListener("message", onMessage);
}
}, []);
useEffect(() => {
messagesEndRef?.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const sendInputMessage = (message) => {
ws.send(message);
setMessages([
...messages,
{
id: Date.now(),
message: data.message,
sentBy: 'user'
}
]);
};
return (
<div className="bg-white">
<div className="scroll">
<ListMessages listMessages={messages} />
<div ref={messagesEndRef} />
</div>
<Input sendInputMessage={sendInputMessage} />
</div>
);
};
export default Chat;

WebRTC using React: Why the remote video is not playing?

I have two services, one is sending video service and the other is receiving video service.
The server only forwards the information and forwards the information to the other party.
The service that receives the video can receive the video at some times, but most of them cannot.
I don't know where the problem occurred, the service that sends the video or the service that receives the video.
caller
import { useEffect, useRef, useState, useCallback } from "react";
import io from "socket.io-client";
function createPeerConnection() {
const myPeerConnection = new RTCPeerConnection();
return myPeerConnection;
}
async function setLocalStream(myPeerConnection, target) {
console.log("setLocalStream");
const webcamStream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
target.srcObject = webcamStream;
webcamStream
.getTracks()
.forEach((track) =>
myPeerConnection.addTransceiver(track, { streams: [webcamStream] })
);
}
const App = () => {
const ref = useRef();
const [socket] = useState(io());
useEffect(() => {
socket.emit("init", "hello");
socket.on("init", (res) => {
console.log(res);
});
}, []);
const handleNegotiationNeededEvent = useCallback(
(myPeerConnection) => async () => {
try {
const offer = await myPeerConnection.createOffer();
await myPeerConnection.setLocalDescription(offer);
console.log(
"send video-offer",
myPeerConnection.localDescription
);
socket.emit("video-offer", myPeerConnection.localDescription);
} catch (error) {
console.error(error);
}
},
[socket]
);
const handleICECandidateEvent = useCallback(
(event) => {
if (event.candidate) {
console.log("send new-ice-candidate", event.candidate);
socket.emit("new-ice-candidate", event.candidate);
}
},
[socket]
);
useEffect(() => {
const myPeerConnection = createPeerConnection();
myPeerConnection.onicecandidate = handleICECandidateEvent;
myPeerConnection.onnegotiationneeded = handleNegotiationNeededEvent(
myPeerConnection
);
setLocalStream(myPeerConnection, ref.current);
socket.on("video-answer", async (sdp) => {
console.log("received video-answer", sdp);
const desc = new RTCSessionDescription(sdp);
try {
await myPeerConnection.setRemoteDescription(desc);
} catch (error) {
console.error(error);
}
});
socket.on("new-ice-candidate", async (c) => {
console.log("received new-ice-candidate", c);
const candidate = new RTCIceCandidate(c);
try {
await myPeerConnection.addIceCandidate(candidate);
} catch (error) {
console.error(error);
}
});
}, []);
return (
<>
<video style={{ width: "10px" }} ref={ref} autoPlay muted />
</>
);
};
export default App;
callee
import { useEffect, useRef, useState, useCallback } from "react";
import io from "socket.io-client";
function createPeerConnection() {
const myPeerConnection = new RTCPeerConnection();
return myPeerConnection;
}
const App = () => {
const ref = useRef();
const [socket] = useState(io());
useEffect(() => {
socket.emit("init", "hello");
socket.on("init", (res) => {
console.log(res);
});
}, []);
const handleICECandidateEvent = useCallback(
(event) => {
if (event.candidate) {
console.log("send new-ice-candidate", event.candidate);
socket.emit("new-ice-candidate", event.candidate);
}
},
[socket]
);
const handleTrackEvent = useCallback((event) => {
ref.current.srcObject = event.streams[0];
}, []);
useEffect(() => {
const myPeerConnection = createPeerConnection();
myPeerConnection.onicecandidate = handleICECandidateEvent;
myPeerConnection.ontrack = handleTrackEvent;
socket.on("video-offer", async (sdp) => {
console.log("received offer", sdp);
const desc = new RTCSessionDescription(sdp);
await myPeerConnection.setRemoteDescription(desc);
await myPeerConnection.setLocalDescription(
await myPeerConnection.createAnswer()
);
console.log("send answer", myPeerConnection.localDescription);
socket.emit("video-answer", myPeerConnection.localDescription);
});
socket.on("new-ice-candidate", async (c) => {
console.log("received new-ice-candidate", c);
const candidate = new RTCIceCandidate(c);
try {
await myPeerConnection.addIceCandidate(candidate);
} catch (error) {
console.error(error);
}
});
}, []);
return (
<>
<video style={{ width: "20px" }} ref={ref} autoPlay />
</>
);
};
export default App;

Separate functions which depend on each other

I am trying to clean up my code an separate into functions that only have one task.
In v1 joinDailyCo(url); was defined inside fetchUrl(). Now I tried to move it out with
const url = fetchUrl();
joinDailyCo(url);
However, as soon I do that, I get the error message:
Unhandled Rejection (TypeError): Cannot read property 'join' of
undefined
const Daily = ({ eventSlug, tableId }) => {
const classes = useStyles();
const iframeRef = useRef();
const dailyRef = useRef();
const joinedRef = useRef();
useEffect(() => {
// Join call
const joinDailyCo = async (url) => {
if (joinedRef.current) {
// This is needed due to it never returning if there wasn't a meeting joined first.
await dailyRef.current.leave();
}
await dailyRef.current.join({ url });
};
// Retrieve dailySessionId and meetingToken.
const fetchUrl = async () => {
try {
const {
data: { dailySessionId, meetingToken },
} = await api.get(
`events/${eventSlug}/space/tables/${tableId}/daily-auth/`
);
const url = `${DAILY_URL}/${dailySessionId}?t=${meetingToken}`;
return url;
// joinDailyCo(url);
} catch (error) {
Sentry.captureException(error);
}
};
const url = fetchUrl();
url && joinDailyCo(url);
}, [eventSlug, tableId]);
useEffect(() => {
dailyRef.current = DailyIframe.wrap(iframeRef.current, {
// showLeaveButton: true,
});
dailyRef.current.on(eventTypes.LEFT_MEETING, () => {
joinedRef.current = false;
});
dailyRef.current.on(eventTypes.JONING_MEETING, () => {
joinedRef.current = true;
});
return () => {
dailyRef.current.destroy();
};
}, []);
return (
<iframe
ref={iframeRef}
className={classes.root}
title="Video Meeting"
allow="camera; microphone; display-capture; fullscreen"
/>
);
};
export default Daily;

Resources