I'm trying to listen to changes from Sails socket in the front-end using react.
Server listen to changes in a MongoDB Collection and blasts the changes:
// Setting up connection to MongoDB
const RTPEntryDB = sails.getDatastore("RTPEntrydb").manager;
// Switching to the appropriate collection in MongoDB
const profilesCollection = RTPEntryDB.collection("workloads");
// MongoDB Change Stream - Detect changes in DB's collection
const changeStream = profilesCollection.watch();
changeStream.on("change", function(change) {
console.log("new change", change);
sails.sockets.blast('newWorkloads', change);
})
This is working perfectly fine.
But on the front-end, I couldn't listen
import React, { useRef, useReducer, useEffect } from 'react';
import PropTypes from 'prop-types';
import socketIOClient from 'socket.io-client';
import sailsIOClient from 'sails.io.js';
const FetchWorkloads = (props) => {
// Instantiate the socket client (`io`)
const io = sailsIOClient(socketIOClient);
io.sails.url = 'http://localhost:1337/';
io.socket.on('connect', function onConnect(){
console.log('This socket is now connected to the Sails server!');
});
io.socket.on('newWorkloads', (msg) => {
console.log('workloads changed', msg);
});
return (
<>
{/* My Component */}
</>
);
}
FetchWorkloads.propTypes = {
api: PropTypes.object.isRequired
};
export default FetchWorkloads;
I'm getting the error GET http://localhost:1337/__getcookie net::ERR_ABORTED 404 (Not Found). How do I resolve this?
Thanks
I resolved this by defining a custom __getcookie in routes.js as follows:
'GET /__getcookie': (req, res) => {
return res.send('_sailsIoJSConnect();');
},
There was also another issue with the compatibility between sails.io.js and socket.io-client. I was using the latest version of socket.io-client (v4.4) but sails was only compatible with v2.x.
Related
My websocket works because i tested it from django side white simple chat app. The route also works which is http://localhost:8000/chat/room. But It doesnt work on react side. It says No route found for path 'chat/myroom'. I've been trying to solve this for 2 hours, as a last hope, I wanted to ask here.
My routing.py file
from django.urls import re_path
from api import consumers
websocket_urlpatterns = [
re_path(r'ws/chat/(?P<room_name>\w+)/$', consumers.ChatConsumer.as_asgi()),
]
My roomPage.js file
`import React from "react";
import useWebSocket, { ReadyState } from "react-use-websocket";
import { useParams } from "react-router-dom";
export default function RoomPage() {
const { readyState } = useWebSocket("ws://127.0.0.1:8000/chat/myroom", {
onOpen: () => {
console.log("Connected!");
},
onClose: () => {
console.log("Disconnected!");
}
});
const connectionStatus = {
[ReadyState.CONNECTING]: "Connecting",
[ReadyState.OPEN]: "Open",
[ReadyState.CLOSING]: "Closing",
[ReadyState.CLOSED]: "Closed",
[ReadyState.UNINSTANTIATED]: "Uninstantiated"
}[readyState];
return (
<div>
<span>The WebSocket is currently {connectionStatus}</span>
</div>
);
}`
I tried on django side and it worked! But react side doesn't working
After +10 hours i finally found the solution and I want to kill myself! The only problem is calling websocket without "end slash"
Wrong!
useWebSocket("ws://127.0.0.1:8000/chat/myroom"
Works
useWebSocket("ws://127.0.0.1:8000/chat/myroom/"
can i get help with something im stuck on for awhile
im establishing a socket .io connection in my frontend with react context,
but the connection is triggered as soon as my web load, i want it to connect only AFTER i login
im running a jwt auth on my socket connection and what happens is it fails to auth because i dont have a chance to login before it checks it
and then after i log in it wont establish connection untill i manually reload the page...
😦
client:
import React from 'react';
import io from 'socket.io-client'
import { SOCKET_IO_URL } from '../api/config';
const jwt = JSON.parse(localStorage.getItem('profile'))?.token
console.log(jwt)
export const socket = io.connect(SOCKET_IO_URL, {query: {token: jwt}});
export const SocketContext = React.createContext()
server:
io.on('connection', (socket => {
const token = socket.handshake.query.token;
try {
if (!token) throw new Error("TOKEN NOT FOUND")
jwt.verify(token,'secret')
} catch (error) {
console.log('VERIFICATION FAILED')
socket.disconnect()
}
console.log('socket conected', socket.id)
socket.on('request_fetch', () => {
socket.broadcast.emit('response_fetch')
})
}))
You can import the socket / use the socket only in areas where you are logged in.
And lazy import those components
const Dashboard = lazy(() => import('./pages/Dashboard'))
that way the token will always be available when loading those components
According to the docs in the client options, there is an option autoConnect
Client Options
Set this to false as it defaults to true then manually try to connect after you have logged in with socket.connect
I have an web app with multiple features like private messaging, buying, offers etc. I want to make it to work real time so I decided to use socket.io. I use redux for global state management, but I don't know how can I combine this with socket.IO. This was my idea:
1.Creating a file for socket handling with with exported functions to App.js to create a socket connection, sending and listening different data.
2.Whenever I got something relevant for example a notification or a buying request I update my redux state.
3.Finally in my components I will use useEffect for those global redux states and if it changes I will rerender my component based on my changed state.
Is this a good approach? If not which is a proper way to globally mangage my components based on socket recieved informations?
In general, depending on your needs I see nothing wrong with this approach. I will provide one actionable example here. My example will assume TypeScript as it's easier to transform to JavaScript (in case you do not use TypeScript) than the other way around.
In relation to your 1st question I would suggest to establish and pass Websocket connection as a context as you use it everywhere in your application and create custom hook to use the connection anywhere:
import React, { createContext, FunctionComponent, ReactNode, useContext, useEffect, useMemo, useState } from 'react';
import io from 'socket.io-client';
export const WebsocketContext = createContext<SocketIOClient.Socket | null>(null);
const WebsocketProvider: FunctionComponent<{ children: ReactNode }> = ({ children }: { children: ReactNode }) => {
const [connection, setConnection] = useState<SocketIOClient.Socket | null>(null);
const options: SocketIOClient.ConnectOpts = useMemo(() => ({}), []);
useEffect(() => {
try {
const socketConnection = io(process.env.BASE_URL || '127.0.0.1', options);
setConnection(socketConnection);
} catch (err) {
console.log(err);
}
}, [options]);
return <WebsocketContext.Provider value={connection}>{children}</WebsocketContext.Provider>;
};
export const useWebsocket = (): SocketIOClient.Socket | null => {
const ctx = useContext(WebsocketContext);
if (ctx === undefined) {
throw new Error('useWebsocket can only be used inside WebsocketContext');
}
return ctx;
};
export default WebsocketProvider;
Above we create context which has type SocketIOClient.Socket and defaults to null, as when connection is not yet ready we must assign default value. Then we create Websocket provider as FunctionComponent which accepts children(s) and holds connection state with useState hook eventually returning provider with Websocket connection. I also mention SocketIOClient.ConnectOpts as depending on your needs you might want to provide connection options; either statically or dynamically when using the hook. Furthermore useEffect hook which will try to establish the connection or throw an error. The only dependency which will rerun this hook is connection options in case they will dynamically change.
Finally we have custom hook useWebsocket which we can import in any component and use inside our context provider. Simply wrap your root component (or any other hierarchy level) with context provider to provide the context like in the example below:
import React, { FunctionComponent } from 'react';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import { v4 as uuid } from 'uuid';
import routes from './App.routes';
import WebsocketProvider from './websocket.context';
const App: FunctionComponent = () => {
return (
<WebsocketProvider>
<Router>
<Switch>
{routes.map((route) => (
<Route key={uuid()} {...route} />
))}
</Switch>
<Redirect to='/' />
</Router>
</WebsocketProvider>
);
};
export default App;
In relation to your 2nd question you can for example have ´useEffect´ hook to react when connection emits and update your Redux (or other global state management) store. Here I also use Elvis operator to check if the connection is not ready yet (if its not ready yet as null the useEffect hook will re-render on socket connection change when its ready):
import React, { FunctionComponent, useEffect, useState } from 'react';
import { useWebsocket } from './websocket.context';
const Foo: FunctionComponent = () => {
const dispatch = useDispatch();
const socket = useWebsocket();
useEffect(() => {
socket?.on('myEmitEvent', (data: myEmitData) => {
dispatch(myStoreAction(data));
});
return () => {
socket?.off('myEmitEvent');
};
}, [socket, dispatch]);
return ...
};
export default Foo;
In relation to your 3rd question as you mention you can use useEffect hook or more simply useSelector hook from react-redux package which automatically captures your state changes triggering re-render on necessary elements.
In short, your idea hits the ballpark and I hope that with this brief actionable example you will be able to refine solution which works for you.
I've been told that Axios is how you get React to talk to an api (external or internal). So far, I have specifically only received 404 errors whenever I try to implement Axios calls.
Here is the axios call in client/src/App.js:
import React, {Component} from 'react';
import './App.css';
import API from "./utils/API";
class App extends Component {
state = {
recipes: []
}
componentDidMount = () => {
API.getRecipes("milk") /* This is supposed to call the getRecipes
function in API.js with "milk" as the only
parameter (ie - Search the api for "milk"
related recipes). */
.then(res => this.setState({recipes: res.data}))
.catch(err => console.log(err));
}
render(){
return (
<div>
{
this.state.recipes.map(recipe => {
return(
<p>
{recipe.title} // All recipe names are then set to a p tag
</p>
)
})
}
</div>
);
}
}
export default App;
Now, this calls API.js in the "utils" folder:
import axios from "axios";
// Function that takes the parameter and is supposed to send it to the
/api/recipes route
export default {
getRecipes: function(query) {
return axios.get("/api/recipes", { params: { q: query } });
}
};
The relevant api route (/api/recipes) is located in a folder named "routes" outside of the "src" folder. This file is the only item inside the folder.
const axios = require("axios");
const router = require("express").Router();
/* As you can see, this sends the request to "recipepuppy.com" with the
relevant query ("milk").*/
router.get("/recipes", (req, res) => {
axios.get("http://www.recipepuppy.com/api/", {params: req.query})
.then(({data: {results}}) => {res.json(results)})
.catch(err => res.status(422).json(err));
});
module.exports = router;
Going even further out, here is the server.js file (outside of the "client" folder) that determines the routes:
const express = require("express");
const path = require("path");
const PORT = process.env.PORT || 3001;
const app = express();
const apiRoutes = require("./routes/apiRoutes"); // *********
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
if (process.env.NODE_ENV === "production") {
app.use(express.static("client/build"));
}
app.use("/api", apiRoutes); // *********
app.get("*", function(req, res) {
res.sendFile(path.join(__dirname, "./client/build/index.html"));
});
app.listen(PORT, function() {
console.log(`API server now listening on port ${PORT}.`);
});
As far as I can tell, everything is set up perfectly. However, every single time I boot up the server, the browser console error pops up and says:
GET http://localhost:3000/api/recipes?q=milk 404 (Not Found)
Even though server.js directly ties to the apiRoutes folder, and the axios call within API.js calls the exact same route that would result from going to the /api route, then the /recipes route within /api (resulting in /api/recipes).
If anybody here can tell me what is going on and how to fix it, I would appreciate it.
I didn't have "proxy" set in my dependencies (package.json) in the client folder.
Once I set "proxy" to "localhost:3001" (the same as my initial server.js port value) and restarted the server, it worked immediately.
I have almost googled my fingers off trying to figure this out. It seems a lot of the existing info on connecting socket.io with React Native is outdated, or maybe I'm just interpreting things wrong?
I've managed to get the client-side connected (I'm getting the client console logs when I connect to my app). It seems to be the server-side that's giving me issues. Why is the data being emitted from the client not showing up as a log in my terminal? None of the related console.logs in my server.js are logging but the App.js console.logs are registering.
Edit: Here is my full App.js file:
import Expo from 'expo';
import React from 'react';
import { Dimensions, StyleSheet, Text, View } from 'react-native';
import store from './src/store';
import { Provider } from 'react-redux';
// window.navigator.useragent = 'react-native'; -> not necessary anymore?
const ROOT_URL = 'https://myherokudomain.herokuapp.com';
const io = require('socket.io-client/dist/socket.io');
const socket = io.connect(ROOT_URL);
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('example', (data) => {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
socket.on('disconnect', () => {
console.log('Disconnected from server');
});
export default class App extends React.Component {
render() {
// const MainNavigator = my react-navigation system
return (
<Provider store={store}>
<View style={styles.container}>
<MainNavigator />
</View>
</Provider>
);
}
}
Edit: Here is my full server.js file:
const config = require('./config/config');
const { mongoose } = require('./db/mongoose');
const express = require('express');
const cors = require('cors');
const app = express();
const port = process.env.PORT;
// ************ Include and use separate routes file
app.use(require('./routes/routes'));
// ************
//Cross-Origin resource sharing. cors library solves CORS problems.
app.use(cors());
//***********
/* Chat server code*/
// enabled heroku session affinity:
// see https://devcenter.heroku.com/articles/session-affinity
// to enable: heroku features:enable http-session-affinity
// to diable: heroku features:disable http-session-affinity
const socketIO = require('socket.io');
const http = require('http');
const server = http.createServer(app);
const io = socketIO(server, { origin: "*:*" });
//********** */
io.on('connection', (socket) => {
console.log('A client just joined', socket.id);
socket.emit('example', { hello: 'world' });
socket.on('my other event', (data) => {
console.log(data);
});
socket.on('disconnect', () => {
console.log('User was disconnected');
});
});
server.listen(port, (err) => {
console.log(`started on port ${port}`);
});
module.exports = { app };
I am getting the console logs on the client side just fine (for instance, the "connected to server" and "hello: world" stuff is showing up when I open my app on expo. But I am not getting the server-side console logs.
What am I doing wrong - how do I get socket.io fully working with a deployed React-Native app?
I would really appreciate any help at all! I've been stuck on this forever.
I'm assuming all the code works, just not the logging since that's all you're asking about. The problem is Node doesn't output to your browser's console.
If it's deployed on heroku then you should see everything being logged there, otherwise you can use libraries like https://github.com/node-inspector/node-inspector to output to your browser.
You're not getting the server-side console logs because 1.) They're only logging on the server, and 2.) You're not emitting them, if you do actually want to send the data back.