socket.io-client + react, where to connect to the server? - node.js

I'm writing a very simple Nodejs app. I use React + Socket.io.
There's a root element which immediately renders another react component (you may wonder why I have this root element. The reason is that I want to be able to mount one of the two components after receiving a message from server, but for the start I render a preselected component).
In this root component , I define a socket in componentDidMount . Now the problem is that I want to pass this socket to all of the children (so they can listen and communicate with the server messages.) But if I connect to the server in componentDidMount of the root, during the rendering there is no socket as it's not connected yet and null will be passed to the child components.
'use strict';
var React = require('react');
var ioClient = require('socket.io-client');
var UsersList = require('./usersList');
var Game = require('./game');
var socket;
var Snake = React.createClass({
displayName: 'Snake',
propTypes: {},
getDefaultProps: function() {
return {};
},
mixins: [],
getInitialState: function() {
return ({
usersList: true,
game: false
});
},
componentWillMount: function() {
},
componentWillUnmount: function() {
this.socket.close();
},
componentDidMount: function() {
socket = ioClient.connect(); // this happens after render
},
render: function() {
var result = null;
if (this.state.usersList) {
result = <UsersList socket={socket}/> // therefore this one is passed as null
} else { //game : true
result = <Game socket={socket}/>
}
return (<div>
{result}
</div>)
}
});
module.exports = Snake;
'use strict';
var React = require('react');
var UsersList = React.createClass({
displayName: 'UsersList',
propTypes: {},
getDefaultProps: function() {
return {};
},
mixins: [],
getInitialState: function() {
return ({
usersList:[]
});
},
componentWillReceiveProps: function(){
},
componentWillMount: function() {
},
componentWillUnmount: function() {
},
componentDidMount: function(){
var socket = this.props.socket; // this one was passed into the component as null
socket.on('usersList', function(data){ // so this one returns an error
this.setState({
usersList: data.usersList
});
});
},
render: function() {
var users = [];
for (var i = 0 ; i < this.state.usersList.length ; i++){
users.push(<span>{this.state.usersList[i]}</span>);
}
return(<div>{users}</div>);
}
});
module.exports = UsersList;
So , now you may ask why I don't put io.connect() in componentWillMount or at the top of the file. Well , it doesn't work ! it returns this error : Cannot find property "protocol" ....
I cannot put it in render , componentWillMount , top of the file ...
Any idea on how to do this ?

You could continue to connect in componentDidMount. It will not be immediately available to the component's children, but then you could do something like this in the children:
componentDidUpdate(prevProps, prevState) {
if ( this.props.socket ) {
// do your connection logic here
}
}
This will ensure that the children immediately connect when the socket is first connected and available to them. Inside the if statement you could also verify that this.props.socket is not equal to prevProps.socket to prevent a redundant connection attempt.

Related

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!!!!!

Why socket.io makes server's CPU 100%? Nuxt+koaJS

I have problem with SocketIO + NuxtJS.
In Production Mode, sometimes server's CPU Usage is suddenly 100% since I attatch socketIO server. Please help me if you know about this problem. thx..
Maybe I guess this problem is related to disconnect event "Transport Polling". I debugged socket io server logs, last log's lines of all were user disconnected: H2X9WQSD6DSzMFT-AAEO, polling.
My Backend Code
// server.js
…
const http = require('http')
const app = new Koa()
const socket = require('Socket.IO')
…
const server = http.createServer(app.callback())
const io = socket(server)
…
io.sockets.on('connection', function(socket) {
console.log('a user connected’)
socket.on('message', message => {
io.sockets.emit('message', message)
})
socket.on('disconnect', () => {
console.log('user disconnected: ' + socket.id + ', ' + socket.handshake.query.transport)
})
})
server.listen(port)
…
My FrontEnd Code
// plugin/socket.js
import Vue from 'vue'
import io from 'Socket.IO-client'
Vue.prototype.$socket = io(process.env.HOST_URL)
// nuxt.config.js
...
plugins: [
{ src: '~/plugins/socket.js', ssr: false },
],
...
// pages/test.vue
...
export default {
data() {
return { messages: [], message: '' }
},
mounted() {
this.$socket.on('message', message => {
this.messages.push(message)
})
},
beforeDestroy() {
this.$socket.off('message')
},
methods: {
//chat to server
handleChat() {
if (this.message) {
this.$socket.emit('message', this.message)
this.message = ''
}
},
}
}
CPU 100% Image

Marionette.js Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'

Here is my LayoutView:
define(["marionette", "lodash", "text!fonts/template.html",
"fonts/controls/view", "fonts/products/view"],
function(Marionette, _, templateHTML, ControlsView, ProductsView) {
'use strict';
var FontsView = Marionette.LayoutView.extend({
regions: {
controls: '#controls',
products: '#products-list'
},
template: _.template(templateHTML),
onRender: function() {
this.getRegion('controls').show(new ControlsView());
this.getRegion('products').show(new ProductsView());
}
});
return FontsView;
});
Here is my ProductsView:
define(["marionette", "lodash", "text!fonts/products/template.html",
'fonts/products/item-view', 'fonts/products/collection'],
function(Marionette, _, templateHTML, ProductItemView, ProductsCollection) {
'use strict';
var ProductsView = Marionette.CompositeView.extend({
el: '.items',
template: _.template(templateHTML),
childView: ProductItemView,
initialize: function() {
this.collection = new ProductsCollection();
}
});
return ProductsView;
});
The error (from the console), is occurring on the this.getRegion('products').show(new ProductsView());
Remove el: '.items', from the ProductsView and it will work. Marionette is already managing the region and gets confused when el is specified on a child view.

backbone.js and express: trouble searching a mongodb collection by field with a query string

I am new to backbone, express, and mongodb.
I am trying to pass a query string to search a mongodb collection by field.
I am doing something wrong. If I comment out the "fetch" from my router, the page is found.
If I try to fetch, then I get a page not found error.
I've tried to isolate where it's breaking, but the backbone architecture is still confusing to me. Thanks in advance. (I'm betting it's a syntax issue in my mongodb call)
kristin
Here is my code.
this URL should return a collection where "type" = 3.
localhost:8888/#content/3
model/models.js:
window.Content = Backbone.Model.extend({
urlRoot: "/content",
idAttribute: "_id"
});
window.ContentCollection = Backbone.Collection.extend({
model: Content,
url: "/content"
});
views/content.js
window.ContentListView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
//return this;
this.$el.append('<ul class="thumbnails">');
this.collection.each(function(model) {
this.$('.thumbnails').append(new ContentView({model: model}).render().el);
}, this);
return this;
} });
window.ContentView = Backbone.View.extend({
tagName: "li",
initialize: function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
views/main.js
var AppRouter = Backbone.Router.extend({
routes: { "content/:type" : "contentType" },
contentType: function(type) {
var contentList = new ContentCollection({type : type});
contentList.fetch({success: function(){
$("#content").empty().append(new ContentListView({collection: contentList}).el);
}});
this.headerView.selectMenuItem('build-menu');
},
utils.loadTemplate([
'ContentView'
], function() {
app = new AppRouter();
Backbone.history.start(); });
contentView.html
name (<% tag won't print here)
routes/modules.js
exports.findContentByType = function(req, res) {
var type = req.params.type;
db.collection('content', function(err, collection) {
collection.find({'type': type.toString()}).toArray(function(err, items) {
res.send(items);
});
});
};
server.js
app.get('/content/:type', module.findContentByType);
I can see a couple of problems here:
this.headerView.selectMenuItem('build-menu'); (in the router) implies you've defined headerView in the router object, but it's not defined.
Similarly, this.template inside ContentView is not defined
When I remove the line in #1, and and define a dummy template in ContentView:
template: _.template("<div> Test: <%= version %> </div>"),
Then the view at least renders -- see here. (This is with dummy data -- I can't confirm that your server is returning valid/expected JSON.)

populating menus from multiple collections

I'm new to backbone.js and express and I have been adapting Christophe Coenraets Wine Cellar REST API example application for my own project.
I am building a form that has several menus needing to be populated from multiple unrelated collections in mongodb.
I am able to populate one menu with one collection, but I have no idea how to get more than one collection to my form View.
Here are the files I am using to populate one menu. How do I expand this to populate two menus?
I suppose I could make a new View for every menu I want to populate - but that seems like overkill.
Can I combine two mongodb find() collections into one object, and list them separately on a page? If so, how?
thanks in advance!
/routes/modules.js contains:
exports.findAllModules = function(req, res) {
db.collection('modules', function(err, collection) {
collection.find().toArray(function(err, items) {
res.send(items);
});
});
};
/server.js contains:
app.get('/modules', module.findAllModules);
/public/js/main.js contains:
routes: {
"modules" : "list" }
...
list: function(page) {
var p = page ? parseInt(page, 10) : 1;
var moduleList = new ModuleCollection();
moduleList.fetch({success: function(){
console.log('in list function');
$("#content").html(new ModuleListView({model: moduleList, page: p}).el);
}});
this.headerView.selectMenuItem('home-menu');
},
...
utils.loadTemplate([
'ModuleListItemView' ], function() {
app = new AppRouter();
Backbone.history.start(); });
/public/models/models.js contains:
window.Module = Backbone.Model.extend({
urlRoot: "/modules",
idAttribute: "_id",
initialize: function () {
this.validators = {};
this.validators.name = function (value) {
return value.length > 0 ? {isValid: true} : {isValid: false, message: "You must enter a name"};
};
validateItem: function (key) {
return (this.validators[key]) ? this.validators[key](this.get(key)) : {isValid: true};
},
validateAll: function () {
var messages = {};
for (var key in this.validators) {
if(this.validators.hasOwnProperty(key)) {
var check = this.validators[key](this.get(key));
if (check.isValid === false) {
messages[key] = check.message;
}
}
}
return _.size(messages) > 0 ? {isValid: false, messages: messages} : {isValid: true};
},
defaults: {
_id: null,
name: ""
} });
window.ModuleCollection = Backbone.Collection.extend({
model: Module,
url: "/modules"
});
/public/js/views/modulelist.js contains:
window.ModuleListView = Backbone.View.extend({
initialize: function () {
this.render();
},
render: function () {
var modules = this.model.models;
$(this.el).html('<ul class="thumbnails"></ul>');
for (var i = 0; i < modules.length; i++) {
$('.thumbnails', this.el).append(new ModuleListItemView({model: modules[i]}).render().el);
}
return this;
} });
window.ModuleListItemView = Backbone.View.extend({
tagName: "li",
initialize: function () {
this.model.bind("change", this.render, this);
this.model.bind("destroy", this.close, this);
},
render: function () {
$(this.el).html(this.template(this.model.toJSON()));
return this;
} });
/public/tpl/ModuleListView.html contains:
Not entirely sure how your code works, but here are a few backbone tips.
If you wanna build a menu from a collection don't pass the collection as a model.
Instead of:
$("#content").html(new ModuleListView({model: moduleList, page: p}).el);
Use:
$("#content").empty().append(new ModuleListView({collection: moduleList, page: p}).el);
Instead of:
render: function () {
var modules = this.model.models;
$(this.el).html('<ul class="thumbnails"></ul>');
for (var i = 0; i < modules.length; i++) {
$('.thumbnails', this.el).append(new ModuleListItemView({model: modules[i]}).render().el);
}
return this;
}
Use:
render: function () {
this.$el.html('<ul class="thumbnails">');
this.collection.each(function(model) {
this.$('.thumbnails').append(new ModuleListItemView({model: model}).render().el);
}, this);
return this;
}
If you have no need in updating or deleting your models, it's enough to add the url path /modules only to the collection, for reading the initial modules.

Resources