server side
io.on('connection', function(socket){
socket.on('user joined', function(data){
socket.join(data.username);
});
socket.on('disconnect', function(){
console.log('user disconnected');
});
socket.on('send private', function(data){
console.log('sending private', data);
io.in(data.to).emit('pmessage',data);
})
});
client side
handleSubmit(){
var newMSG= {
to: this.state.selectedFriend,
from: this.props.user.username,
message: 'new message yay'
}
this.state.socket.emit('send private', newMSG);
}
componentDidMount(){
var self = this;
self.state.socket.on('pmessage', function(data){
console.log('receiving private!', data);
document.getElementById('myprivates').innerHTML= "<p><em>"+data.message+"</em></p>";
})
}
I can't get the 'pmessage' emit event to trigger, so I'm thinking I'm not doing the io.in() part right. I'm trying to implement private communication using the users' usernames but I can't find clear examples or documentation on this. I followed the docs on socket io so I should be close or working but it's not.
It seems like you have used the right react life cycle method but the way you are adding the data to the dom is not a good method in react. Try something like this.
import React from 'react';
import ReactDOM from 'react-dom';
const socket = io();
class MyComponent extends React.Component {
constructor() {
super();
this.state = { messages: []}
}
handleSubmit(){
var newMSG= {
to: this.state.selectedFriend,
from: this.props.user.username,
message: 'new message yay'
}
this.state.socket.emit('send private', newMSG);
}
componentDidMount(){
var self = this;
self.state.socket.on('pmessage', function(data){
console.log('receiving private!', data);
this.setState({messages:this.state.messages.concat(data)});
}
messagelist(){
return this.state.messages.map((msg,i) => {
return <li key={i}> msg </li>
});
}
render() {
let messages =
return (
<div>
<h1>Messages</h1>
<div>
{this.messagelist}
</div>
</div>
);
}
}
export default MyComponent;
The way we need to listen to new messages from socket has to be handled separately and make the component state to change with the new data.
I have added a simple example on how to do this. It may not work as it is and need some changes based on your business logic. But this should give you a fair idea on how to do it.
I didn't add a case where client talks to the server with a new message.But it's fairly simple to implement.
import React from 'react';
import ReactDOM from 'react-dom';
const socket = io();
class MyComponent extends React.Component {
constructor() {
super();
this.state = { messages: []}
socket.on('pmessage', (newMessages) => this.handleStateChange(newMessages));
}
handleStateChange(newMessages) {
this.setState({ messages: newMessages });
}
render() {
let messages = this.state.messages.map((msg) => {
return <li> msg </li>
});
return (
<div>
<h1>Messages</h1>
<div>
{messages}
</div>
</div>
);
}
}
ReactDOM.render(<MyComponent/>, document.getElementById('private-messages'));
Related
im building a chat app with mern and socket.io, i tried to emit with callback but it emit function not value and when i delete setList(l=>[...l,data]) it emit (first problem).
And the second problem, when i recieve the message and push it to list twice (useEffect).
Client side:
import React,{ useState,useEffect } from 'react';
import socketIOClient from "socket.io-client";
function Room(props) {
const [message,setMessage]=useState('');
const [list, setList] = useState([]);
const ENDPOINT = "http://localhost:5000";
const socket = socketIOClient(ENDPOINT, {
credentials: true
});
const sendMessage=(e)=>{
e.preventDefault();
let data=message;
//the first problem is here
socket.emit('send',(data)=>{
setList(l=>[...l,message])
});
}
useEffect(()=>{
//the second problem is here
socket.on('rec',(data)=>{
setList(l=>[...l,data])
})
},[])
return (
<div>
<div>
{list!==[] && list!==null && list.map((el,i)=>{
return <p key={i}>{el}</p>
})}
</div>
<div>
<form>
<input value={message} onChange={e=>setMessage(e.target.value)} />
<button onClick={sendMessage}>Send</button>
</form>
</div>
</div>
)
}
export default Room;
Server Side:
const io = new Server(server, { cors: { origin: '*'}});
app.use((req,res,next)=>{
res.header("Access-Control-Allow-Credentials", true);
next();
})
dotenv.config();
io.on("connection", (socket) => {
socket.emit('greeting', {
greeting: 'Hello Client'
})
socket.on("send", (data) => {
io.emit("rec", data);
console.log(data)
});
});
server.listen(process.env.PORT, err => err?console.log('server error'):console.log(`server is running on PORT ${process.env.PORT}`));
//the first problem is here
socket.emit('send',(data)=>{
setList(l=>[...l,message])
});
has to be
socket.emit('send', message, (data)=>{
setList(l=>[...l,message])
});
(you missed the argument, see https://socket.io/docs/v4/client-api/#socketemiteventname-args)
2)
useEffect(()=>{
//the second problem is here
socket.on('rec',(data)=>{
setList(l=>[...l,data])
})
},[])
it's not really a problem, because you add the same message here and in sendMessage, by using setList twice. I suppose for the chat app you need to change io.emit("rec", data); with a different response.
My socket.io chat app is basically fully functional but the react page is not updating when the message event is emitted to the group id. Currently I am using a map function to run through all of the messages in an array... Each time a message is sent it is saved in a database for future use, but that doesn't seem to be the problem because I removed that part and the react component still didn't update. Any help is greatly appreciated... code below --
// Import React dependencies.
import React, { Component } from "react";
import io from 'socket.io-client'
import axios from 'axios'
import Message from '../Message/Message'
import uuid from 'react-uuid'
// Import the Slate components and React plugin.
const ENDPOINT = 'http://localhost:5000/'
export const socket = io.connect(ENDPOINT)
export class LiveChat extends Component {
constructor() {
super()
this.state = {
messages: [],
message: "",
id: "",
username: ""
}
}
changeHandler = (e) => {
this.setState({
message: e.target.value
})
socket.emit('typing',)
}
clickHandler = () => {
const data = {
messageId: uuid(),
username: this.state.username,
groupId: this.state.id,
message: this.state.message,
}
console.log(data.messageId)
socket.emit('message', data)
}
async componentDidMount() {
const { id } = this.props.match.params
console.log(this.state)
const response = await axios.get('http://localhost:5000/api/users/userInfo', { withCredentials: true })
const messages = await axios.get(`http://localhost:5000/live/${id}`)
console.log(messages.data)
this.setState({
messages: messages.data,
username: response.data.user,
id: id
})
console.log(this.state)
socket.on('typing', data => {
console.log(data)
});
socket.on(`message-${this.state.id}`, data => {
console.log(data)
this.state.messages.push(data)
})
}
render() {
return (
<div>
<input placeholder="message" type="text" onChange={e => this.changeHandler(e)} />
<button onClick={this.clickHandler}>Submit</button>
{this.state.messages.map((message) => (
<Message key={message.messageId} message={message} />
))}
</div>
)
}
}
export default LiveChat;
relevant API code:
io.on("connection", (socket) => {
console.log("New client connected");
socket.on('new-operations', function (data) {
groupData[data.groupId] = data.newValue;
console.log(groupData[data.groupId])
io.emit(`new-remote-operations-${data.groupId}`, data)
})
socket.on('typing-message', function (message) {
io.emit('typing', "user is typing")
})
socket.on('message', function (data) {
console.log(data)
Chat.create({
messageId: data.messageId,
username: data.username,
groupId: data.groupId,
message: data.message
})
io.emit(`message-${data.groupId}`, data)
})
io.emit("init-value", initValue)
socket.on("disconnect", () => {
console.log("Client disconnected");
clearInterval(interval);
});
});
The problem is caused by the fact that setState is an asynchronous function. Because setState doesn't update the state immediately, in your codes, after you setState for id, you read it in the socket.on function, in this case, it is not guaranteed to get the updated state.
socket.on(`message-${this.state.id}`, data => {...
For more info, refer to this link below.
setState doesn't update the state immediately
so I am trying to add to cart. My node.js endpoints are working correctly and I am able to add items to cart when viewed in postman app but it does not display items on the front end, and when inspecting through the chrome developers tools, the items array is empty when on the postman while testing it is successfully storing items.
Here is my server.js
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const cors = require("cors");
const mongoose = require("mongoose");
let Book = require("./models/bookModel");
const port = 4000;
app.use(cors());
app.use(bodyParser.json());
mongoose.connect("mongodb://127.0.0.1:27017/books", { useNewUrlParser: true });
const connection = mongoose.connection;
connection.once("open", function() {
console.log("MongoDB database connection established successfully..");
});
const bookRoutes = express.Router();
app.use("/books", bookRoutes);
const cartRoutes = express.Router();
app.use("/cart", cartRoutes);
bookRoutes.route("/").get(function(req, res) {
Book.find(function(err, books) {
if (err) {
console.log(err);
} else {
res.json(books);
}
});
});
bookRoutes.route("/:id").get(function(req, res) {
let id = req.params.id;
Book.findById(id, function(err, book) {
res.json(book);
});
});
cartRoutes.route("/").get(function(req, res) {
var cart = req.session.cart;
var displayCart = { items: [], total: 0 };
var total = 0;
for (var item in cart) {
displayCart.items.push(cart[item]);
total += cart[item].qty * cart[item].price;
}
displayCart.total = total;
return res.json(displayCart);
});
cartRoutes.route("/:id").post(function(req, res) {
req.session.cart = req.session.cart || {};
var cart = req.session.cart;
let id = req.params.id;
Book.findById(id, function(err, book) {
if (err) {
console.log(err);
}
if (cart[id]) {
cart[id].qty++;
} else {
cart[id] = {
item: book._id,
title: book.title,
price: book.price,
qty: 1
};
}
res.redirect("/cart");
});
});
app.listen(port, function() {
console.log("Server is running on Port: " + port);
});
the server response:
{
"items": [
{
"item": "5dd7668f33c21d811b74f403",
"title": "Modern PHP",
"price": 25.65,
"qty": 1
},
{
"item": "5dd6bb36725bbba1ca482eea",
"title": "Professional Node.js",
"price": 20.56,
"qty": 2
}
],
"total": 66.77
}
cart.js
import React, { Component } from "react";
import axios from "axios";
import CartItem from "./cart1-item.component.js";
import "bootstrap/dist/css/bootstrap.min.css";
import { throws } from "assert";
export default class Cart extends Component {
constructor(props) {
super(props);
this.state = {
items: []
};
}
componentDidMount() {
axios
.get("http://localhost:4000/cart/")
.then(response => {
this.setState({
items: response.data.items
});
console.log(response.data.items);
})
.catch(function(err) {
console.log(err);
});
}
checkItems() {
return this.state.items.map((currItem, i) => {
return <CartItem book={currItem} key={i}></CartItem>;
});
}
Calculate = item => {
return item.qty * item.price;
};
render() {
return (
<div className="container">
<div className="row">{this.checkItems()}</div>
</div>
);
}
}
cartitem.js
import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.min.css";
const CartItem = props => {
return (
<div className="container">
<h2>{props.book.title}</h2>
</div>
);
};
export default CartItem;
here is the app.js code for cart route
<Route path="/cart" exact component={Cart}></Route>
Edited code book-details.component.js
import React, { Component } from "react";
import "../css/styles.css";
import axios from "axios";
export default class BookDetails extends Component {
constructor(props) {
super(props);
this.state = {
book: []
};
}
componentDidMount() {
axios
.get("http://localhost:4000/books/" + this.props.match.params.id)
.then(response => {
this.setState({ book: response.data });
})
.catch(function(err) {
console.log(err);
});
}
AddToCart = e => {
let id = e.currentTarget.getAttribute("id");
axios.post(`http://localhost:4000/cart/${id}`).then(() => {
window.location.href = "http://localhost:3000/cart/";
});
};
render() {
const { book, quantity } = this.state;
return (
<div className="container">
<div className="row">
<div className="col sm-4">
<img src={`./images/${book.cover}`}></img>
</div>
<div className="col sm-8">
<h2>{book.title}</h2>
<ul>
<li>Category: {book.category}</li>
<li>Author: {book.author}</li>
</ul>
<p className="button blue">${book.price}</p>
<p>{book.description}</p>
<button id={book._id} onClick={this.AddToCart}>
Add To Cart
</button>
</div>
</div>
</div>
);
}
}
App.js
import React, { Component } from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import BooksList from "./components/book-list.component.js";
import BookDetails from "./components/book-details.component.js";
import "bootstrap/dist/css/bootstrap.min.css";
import Navigation from "./components/navigation.component";
import Cart from "./components/cart1.component";
class App extends Component {
render() {
return (
<Router>
<Navigation></Navigation>
<Route
path="/"
exact
render={() => (
<div className="container">
<BooksList></BooksList>
</div>
)}
></Route>
<Route path="/books/:id" exact component={BookDetails}></Route>
<Route path="/cart/" exact component={Cart}></Route>
</Router>
);
}
}
export default App;
Any help would be appreciated.
i think i should point out that what you are passing to CartItem is "books" while in the CartItem component you are trying to get "items" from props (this.props.items). that part should be this.props.books.title.
UPDATE:
After you updated your question, i noticed this addition:
and on clicking the add to cart button it navigates to the link
href={"/cart"} className="button"> Add to Cart
this might be where your problem is coming from. on the API, to add books to cart, you did something like this:
cartRoutes.route("/:id").post(function(req, res) {
req.session.cart = req.session.cart || {};
var cart = req.session.cart;
let id = req.params.id;
so you are basically making a post request (even though from the code you are not really posting any data since you are just extracting the id from the url parameter. maybe you should consider making this a get request).
the key part here is the post http method and the id that is expected as the url parameter.
to make things simple on yourself, you can change your "add to cart" to something like:
<button className="button" id={book.id} onClick={this.addToCart}>
Add to Cart
</button>
for addToCart, you can do something like this:
addToCart=(e)=>{
let id = e.currentTarget.getAttribute("id");
axios.post(`http://localhost:4000/cart/${id}`)
.then(()=>{window.location.href = "http://localhost:3000/cart"})
}
note that like i said, you can replace the post request above to a get request since you are not actually posting any form data. if you wish to do this, you should also change the corresponding post request in your api to a get request.
Also, note that you can't get cart items posted through postman from the browser. you are using node sessions for cart items storage. you have to create a different react component (if you have not already created it) from where you can send the post request above to your express api
so I'm making an app where I had to create a nodeJS server and I have to display the API calls on the client side(reactJS). However, when I call the fetch() method, nothing gets displayed on the client side but only on the server side in my terminal. I probably don't know how to do it properly, so I was just wondering if any of you guys have an idea of what I'm doing wrong, I just want to learn, that's all. Here's small part of my server side:
const express = require('express');
const router = express.Router();
var key = "v2xcfdfa76db9f173028d97859c47a8ce0554321029a3fbfc06a26f81b1655bd3d9";
var BitGo = require('bitgo');
var client = new BitGo.BitGo({ env: 'test', accessToken: key });
router.get('/user/me', (req, res, next) => {
client.me({}, function callback(err, user) {
if (err) {
console.dir(err);
}
console.dir(user);
});
});
module.exports = router;
And here's the client side:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: ""
}
}
componentDidMount() {
return fetch('/user/me')
.then((response)=> response.json())
.then((responseJson)=>{
this.setState({
data: responseJson.data
});
})
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Welcome to React</h1>
</header>
<p className="App-intro">
Data from our API: <b>{this.state.data}</b>
</p>
</div>
);
}
}
export default App;
You aren't actually returning anything from the server... or so it seems with the limited code you gave us.
router.get('/user/me', (req, res, next) => {
client.me({}, function callback(err, user) {
if (err) {
console.dir(err);
res.status(400).send(err)
}
console.dir(user);
res.send({ data: user })
});
});
I'm assuming that user is JSON serializable (and that it's the data that you want returned from the server).
I have a Server and Client with sockets, the connection is working between them, but my React component is not refreshing.
My component is a publication that is printed by an array of elements, and I get all this information from and API (Express) connected to a MySQL database.
This is my React file:
import React, { Component } from 'react';
import './component.css';
import io from 'socket.io-client';
import Msg from './components/Msg';
import ItemMsg from './components/ItemMsg';
import Sidebar from './components/Sidebar';
let socket = io('http://localhost:8082');
class App extends Component {
constructor(props){
super(props);
this.state = {
ids: []
};
}
componentDidMount() {
socket.on('update:component', (data) => {
// Debug line
console.log('recived message:', data);
// Update state
this.setState(prevState => {
// Get previous state
const { ids } = prevState;
// Add new item to array
ids.push(data);
// Debug line
console.log('new state:', ids);
// Return new state
return { ids };
});
});
fetch('/todos/ids')
.then((response) => response.json())
.then((responseJson) => {
this.setState({ids: responseJson.data})
console.log(responseJson.data);
})
}
render() {
return (
<div>
<Sidebar/>
<Msg/>
<ul>
{
this.state.ids.map((i, k) =>
<li key={k}><ItemMsg idfromparent={i.ID_Publicacion}/></li>
)
}
</ul>
</div>
);
}
}
export default App;
This is my Server file:
var express = require('express');
var http = require('http');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
var messages = [{
id: 1,
text: "I'm a message",
author: "I'm the author"
}];
app.use(express.static('public'));
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept-Type');
res.header('Access-Control-Allow-Credentials', 'true');
next();
})
app.get('/hello', function(req, res) {
res.status(200).send("Hello World!");
});
io.on('connection', function(socket) {
console.log('Alguien se ha conectado con Sockets');
socket.on('update:component', function(data) {
socket.emit('Thanks', data);
console.log(data);
});
});
server.listen(8082, function() {
console.log("Server corriendo en http://localhost:8082");
});
Insert component:
import React, { Component } from 'react';
import avatar from '../avatar.jpg'
import '../component.css'
import io from 'socket.io-client';
let socket = io('http://localhost:8082');
class Msg extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
handleSubmit(e){
e.preventDefault();
var self = this;
// On submit of the form, send a POST request with the data to the server.
fetch('/todo/meterla',{
method: 'POST',
body:JSON.stringify({
publicacion: self.refs.task.value
}),
headers: {"Content-Type": "application/json"}
})
.then(function(response){
return response.json()
}).then(function(data){
console.log('json:', data);
socket.emit('update:component', data );
});
}
componentDidMount(){
socket.on('update:component', (data) => {
// Update state
this.setState(prevState => {
// Get previous state
const { ids } = prevState;
// Add new item to array
ids.push(data);
// Return new state
return { ids };
});
});
}
handleChange(event) {
this.setState({value: event.target.value});
}
render(data) {
return (
<div>
<form className="post" action="index.html" method="post" onSubmit={this.handleSubmit.bind(this)}>
<img className="avatar" src={avatar} alt="" />
<input type="text" placeholder="Escribe algo" ref="task" value={this.state.value} onChange={this.handleChange}/>
<input type="submit" value="Publicar" />
</form>
</div>
);
}
}
export default Msg;
this is the result of the logs
Issue
I want to automatically refresh the component when I insert a data in the database, and thats why i've implemnted the websockets. The fetch brings me a select from the database
I guess you mean every time you send a post request trough handleSubmit(), right?
Post request
Handle your post request with express:
You need to emit a custom event every time you send the post request:
Emits an event to the socket identified by the string name. Any other parameters can be included.
app.post("/foo", function(req, res, next) {
io.sockets.emit("foo", req.body);
res.send({});
});
See: docs
Get value from input
In HTML, form elements such as <input>, <textarea>, and <select> typically maintain their own state and update it based on user input. In React, mutable state is typically kept in the state property of components, and only updated with setState().
See: Controlled components
// Set state
this.state = {value: ''};
...
handleChange(event) {
this.setState({value: event.target.value});
}
...
// Render
<input type="text" value={this.state.value} onChange={this.handleChange} />
Handling Events
This binding is necessary to make this work in the callback
this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
See: docs
Setup react component + socket.io
const io = require('socket.io-client');
...
class MyComponent extends React.Component {
ComponentWillMount(){
this.socket = io();
}
}
See: Set up React component to listen to socket.io
Listen for event
Register a new handler for the given event.
See: docs
You are only updating the state only one time on fetch(), trigger updateState inside a listener:
ComponentDidMount(){
// Listen for custom event and add handler
this.socket.on('update:component', (data) => {
...
Using state correctly
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
Do Not Modify State Directly, the only place where you can assign this.state is the constructor:
this.setState(prevState => {
// Get previous state
const { ids } = prevState;
// Add new item to array
ids.push(data);
// Return new state
return { ids };
});
}
Render
JSX allows embedding any expressions in curly braces so we could inline the map() result
{
this.state.ids.map((i, k) =>
<li key={k}><ItemMsg idfromparent={i.ID_Publicacion}/></li>
);
}
Github
websockets_react/issues/1
Resources
Socket.io: documentation
React-state: documentation
Npm-package: react-socket-io
Learn: React + socket.io