I am trying to incorporate socket.io in a React application. When the user clicks a button, I want the program to display a modal notifying all other users that the button has been clicked. In my current implementation, I set up the socket.io connection in my server.js file and use socket.io-client in one of the component files to send / listen for information from the server.
Server.js file:
io.on("connection", function(socket) {
console.log("Socket.io connection established");
socket.emit("saved article", function(article){
console.log("article saved");
io.emit("saved article", article);
});
});
Component file:
const socket = io();
class Search extends Component {
state = {
topic: "",
start: "",
end: "",
results: [],
savedModalTriggered: false,
articlesSaved: []
};
componentDidMount(){
socket.on("saved article", article => {
let articlesSavedCopy = this.state.articlesSaved;
articlesSavedCopy.push(article.title);
this.setState({savedModalTriggered: true, articlesSaved: articlesSavedCopy});
});
};
saveOrUnsave = (index) => {
API.saveArticle(this.state.results[index]).then(response => {
const article = {
title: response.data.title
};
socket.emit("saved article", article);
this.reverseSaved(index, response.data);
});
};
};
The following problems arise when I run my code:
1) When the Search component mounts, the program triggers socket.on("saved article"), causing the notification modal to pop up even though the saveOrUnsave function was not called.
2) After some period of time, I get the following error in my console: "WebSocket connection to localhost:3000... failed: Connection closed before receiving a handshake response"
3) I also get the following error in my console: "WebSocket connection to localhost:3000... failed: WebSocket opening handshake timed out"
The problem is that you are emitting a saved article event upon connection. When the client opens a new connection in the componentDidMount callback the server emits an event, thus triggering the callback you have registered.
If that is not what you want you should remove the emit logic from your connection callback in the server code.
Related
I have been trying to simulate file-upload as a test for my react-app but that was generating the following error :
TypeError: Network request failed
at node_modules/whatwg-fetch/dist/fetch.umd.js:535:18
at Timeout.task [as _onTimeout] (node_modules/jsdom/lib/jsdom/browser/Window.js:516:19)
This is my test: trying to upload a file and check if an alert is raised.
test("triggers alert when receiving a file with inadequate content", async () => {
renderComponent();
global.alert = jest.fn();
const fileContent = raw("./file.kml");
const fakeFile = new File(
[fileContent],
"file.kml",
{ type: "text/xml" }
);
const selectType = screen.getByTestId("select-type");
await fireEvent.change(selectType, { target: { value: "type" } });
const fileUploader = screen.getByTestId("file-uploader");
await fireEvent.change(fileUploader, {
target: { files: [fakeFile] },
});
await waitFor(() => {
expect(global.alert).toHaveBeenCalledWith(
"alert"
);
});
});
});
I am kind of confused, because file is received and parsed by the component and it raise the alert I need to check but still fails because of the network error.
PS: I tried to mock a fetch but still have the same problem.
Any help would be appreciated.
I have been getting the same
Network request failed: Connection refused
error while testing.
I have explored many threads but no luck.
Out of the blue starting the back end server worked for me. (Even though I used mock service worker that intercepts network calls, starting the back end server worked.)
I don't know why.
Also, I have used import fetch from 'isomorphic-fetch'; in setup-test.js instead of whatwg-fetch.
In the end try adding a timeout:3000 option to your waitFor.
I am using react to get geolocation on localhost:3000. I have seen another person get the geolocation coordinates on their localhost, but I am unable to do so even with allow location access enabled on Chrome.
I have tried using both the hooks and class syntax in react. I have enabled allow access. I eventually used an ip address api to get a general location, but since the geolocation is supposed to work(at least that is what I have been told) I would at least like to see it work so I can implement it with https in the future. The error log does not even get fired, whereas the first three logs are getting fired when the component is mounted. Here is the code I have tried, I have made it as simple as possible:
const App = props => {
useEffect(() => {
console.log('hello')
console.log(navigator)
console.log(navigator.geolocation)
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
console.log(position)
}, (error) => {
console.log(error)
})
} else {
console.log('error')
}
}, [])
return (
<div>
<h3>Please Login.</h3>
</div>
)
}
export default App
I expect to receive a response from googleapi.
Edit:
I added the error callback and it printed:
message: "Network location provider at 'https://www.googleapis.com/' : No response received."
add the optional error callback to handle the error (if user declines location permission)
navigator.geolocation.getCurrentPosition(success[, error[, [options]])
you are checking only if it is in navigator or not !!!
if user declines location permission then error callback will handle it...
possible options are (reference taken from mdn)
{
enableHighAccuracy: true,
maximumAge : 30000,
timeout : 27000
}
I'm using React with Socket.io and trying to make my component update in real time, so one user can create a new event and it immediately shows up for all users. I've done this before outside of React, and it seems so simple, but I can't get it to work.
Desired behavior: When a user adds a new event, the server sends the new event to the client, where the client sets the new event into the redux store.
Actual behavior: The server emits the event, but the client never receives it. In the network tab, two websocket connections have status 'pending'.
This is my code:
server:
io.on('connection', (socket) => {
socket.on('createEvent', async (event, acknowledge) => {
let err;
let result;
// add event to DB
result = await db.createEvent(event);
if(!result) err = "An error occured during event creation.";
acknowledge(err, result);
console.log('result', result);
if (result) {
socket.emit('eventCreated', result);
console.log('emitted eventCreated');
}
});
});
Client:
componentDidMount () {
this.getEventsFromDB();
//listen for new events
socket.on ('eventCreated', (event) => {
console.log('hello,', event);
this.props.dispatch(addEvent({ event }));
});
};
I found the answer - I was using const socket = io() at the start of each file on the client side where I was using websockets. So each page was getting its own separate socket, which worked just fine until I needed two different pages to have access to the same socket.
I fixed it by instantiating one socket in my main file with my router, and passing it down to each component as a prop or via Redux.
I have my Spring-Boot service setup so I can send messages through websocket to my browser and it works.
//#MessageMapping
#RequestMapping(value = "/notify")
#SubscribeMapping("/notification")
#SendTo("/topic/notification")
public String sendNotification() throws Exception {
sendMessage();
return "Request to update Tanks has been sent!";
}
public void sendMessage() {
this.messagingTemplate.convertAndSend("/topic/notification", "IT WORKS");
}
Here's the console log from chrome:
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-1519225601109-13
message-id:f2qodiqn-8
content-length:8
IT WORKS
I want to be able to receive a message from the service and update the state in react, so, that it refetches from the backend. This is what my client looks like:
var socket = new SockJS("http://localhost:6667/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
console.log(notification.body);
//this.showNotification(JSON.parse(notification.body).content);
//this.showNotification(notification.body);
})
}, function(err) {
console.log('err', err);
});
And the fetch in componentDidMount()
fetch(`http://localhost:6666/front/objects`)
.then(result=>result.json())
.then(fuelTanks=>this.setState({fuelTanks}))
.catch(function(err) {
console.log('Could not fetch: ' + err.message);
}
)
I can't use this.showNotification(notification.body), hence I can't set the state to be able to refetch my objects. I tried making methods outside the class but then I can't use anything from the main class.
Is there a way to make react run componentDidMount again, or better, just access the fetch method in my class when I get a message from spring through the websocket?
Like this:
componentDidMount(){
var socket = new SockJS("http://192.168.1.139:8610/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
refetchTanks(); // call fetch tanks -> can't use "this"
})
}, function(err) {
console.log('err', err);
});
Thanks!
I know, it is a bit old question, but since it pops every time when you search for stomp issue, i thought of answering it. The way to access this in callbacks is to bind callbacks with this first, then the whole of object can be accessed in the callback.
Example:
connectCallBack(){
this.setState({loading:false})
}
errorCallback=()=>{
}
componentDidMount() {
axios.post('http://localhost:8080/subscribe', null, { params: {
deviceId
}})
.then(response => response.status)
.catch(err => console.warn(err));
const socket = new SockJS('http://localhost:8080/test');
const stompClient = Stomp.over(socket);
//stompClient.connect();
stompClient.connect( {}, this.connectCallBack, this.errorCallback);
If see above code both callbacks can access this.
I tried everything to be able to use my class methods and the state in stompClient's .subscribe method. I was able to connect and reconnect if the service died, nevertheless it wasn't working.
I decided to use react-stomp, which worked. I could use a class method in onMessage=.... This is what my code looks like:
<SockJsClient
url = 'http://localhost:8610/refresh/'
topics={['/topic/notification']}
onConnect={console.log("Connection established!")}
onDisconnect={console.log("Disconnected!")}
onMessage={() => this.update()} <------ this method performs a new GET
request
debug= {true}
/>
I also had to send the message in a specific way on the server side, since I was getting a JSON error when sending a string.
this.messagingTemplate.send("/topic/notification", "{"text":"text"}");
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-0
message-id:aaylfxl4-1
content-length:49
{
"text": "text"
}
It currently works, but I am curious if there are other, better solutions to this issue.
EDIT: a much better solution here! Use the code from the first post and create a variable before connect to be able to access this like this var self = this;, then just access is as self.update() after subscribe!
I'm creating a chatboard and I'm using signalR to let users know when new comment is added on some status/post.
I'm using React and I've tried to add event listeners for the hub function in different components, in some components it works, not others.
This is the hub:
public class CommentHub : Hub
{
public void UpdateComments(int postId)
{
try
{
var context = GlobalHost.ConnectionManager.GetHubContext<CommentHub>();
context.Clients.All.updateNewComments(postId);
}
catch (Exception ex)
{
Log.Error(ex.Message);
}
}
}
I call the event listener in the componentDidMound function:
componentDidMount: function() {
this.commentUpdateListener();
},
And this is the event listener:
commentUpdateListener: function () {
console.log("in the comment update listener!");
var commentHub = $.connection.commentHub;
commentHub.client.updateNewComments = function (postId) {
console.log("updateNewComments called!");
};
$.connection.hub.start();
},
I have a react component for a post/status, a component for the "wall/newsfeed" and then I have a component for the "comment box" for each status on the wall, where the comments are rendered.
When I place the event listener in the wall component or the post component it works, and the 'updateNewComponent' function gets called, but not when I place it in the "comment box" component.
Still "in the comment update listener!" gets logged no matter where I place the event listener, but the hub function is not always called, and it seems to matter in what react component the event listener is placed.
Is the $.connection.commentHub not staying alive? Is it closing?
Is there some reason the function is not always being called?
You can enable logging before starting the hub connection like this :
$.connection.hub.logging = true;
$.connection.hub.start();
If you see some disconnections you can try to restart the connection when it closes :
$.connection.hub.disconnected(function() {
setTimeout(function() {
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});
Further reading : Understanding and Handling Connection Lifetime Events in SignalR