I've been trying to develop a realtime chat app using the MERN stack (React-Native instead of React) and was successful, but I cannot convert it to real time using the socket.io library. Below I have provided some code from my project that describes the situation/structure before using socket.io and after:
FLOW
A user registers/login using his phone number, on every first render of home screen, I will fetch the rooms, if the user has any. (rooms => conversations with people, like homescreen of whatsapp). Each room will have a unique roomId and data of these 2 users (refer to the Room schema). Now when he/she will tap to enter the ChatScreen, users can send message (refer Message schema) to eachother. To fetch all messages of a particular chat, I make use of the unique roomId to fetch all the messages having that roomId only. Now, the problem is, when other user sends any message, I have to re-render the whole app to get new messages, therefore, no real-timeness.
SERVER
I have 3 collections in my mongodb, 1) users, 2) rooms 3) messages.
schemas:
const RoomSchema = mongoose.Schema({
roomId: String,
usersId: [String],
users: Object,
}, {
timestamps: true,
});
module.exports = mongoose.model('Room', RoomSchema);
const MessageSchema = mongoose.Schema({
roomId: String,
senderId: String,
text: String,
}, {
timestamps: true,
});
module.exports = mongoose.model('Message', MessageSchema);
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
},
phone: {
type: String,
required: true,
trim: true,
unique: true,
},
otp: String,
}, {
timestamps: true,
});
module.exports = mongoose.model('User', UserSchema);
index.js
const express = require('express');
const mongoose = require('mongoose');
const http = require('http');
const socketio = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketio(server).sockets;
mongoose.connect(process.env.MONGO_URL);
mongoose.connection.on('connected', () => {
console.log('[INFO] Connected to MongoDB');
});
mongoose.connection.on('error', (error) => {
console.log(`[ERROR] ${error}`);
});
// SOCKETS MIDDLEWARE
require('./middlewares/socket')(io);
server.listen(process.env.PORT, () => {
console.log(`[INFO] Server running at ${process.env.PORT}`);
});
socket.js
const Message = require('../models/Message');
module.exports = (io) => {
io.on('connection', (socket) => {
console.log('A user connected.');
socket.on('sent_messages', async ({ roomId }, cb) => {
const messages = await Message.find({roomId});
cb(messages);
});
socket.on('send2user', async (data) => {
socket.broadcast.to(data.roomId).emit();
const message = new Message({
roomId: data.roomId,
senderId : data.senderId,
text: data.text,
});
const result = await message.save();
});
});
};
Now, basically, whenever a user touch on a contact item in frontend, a room will be created (chat room) for these 2 users (private 1-1 chat app). So, now the 2 users are ready to chat real time. The endpoints for fetching and creating messages (Although, I have created a new message in socket.js file but don't know how to proceed further):
router.post('/create_message', async (req, res) => {
const {roomId, senderId, text} = req.body;
try {
const message = new Message({
roomId,
senderId,
text,
});
const result = await message.save();
return res.status(200).json({
type: 'success',
data: result,
});
} catch (error) {
return res.status(422).send({error: `${error.message}`});
}
});
router.post('/get_messages', async (req, res) => {
const {roomId} = req.body;
try {
const messages = await Message.find({roomId});
return res.status(200).json({
type: 'success',
data: messages,
});
} catch (error) {
return res.status(422).send({error: `${error.message}`});
}
});
FRONTEND
utility.js
export const socket = io(API_URL, {forceNew: true});
socket.on('connection', () => {
console.log('Connected to server');
});
export const fetchMessages = (roomId, setMessages) => {
// socket.emit('sent_messages', {roomId}, (data) => {
// setMessages(data);
// });
AsyncStorage.getItem('token')
.then(token => {
if (token) {
fetch(`${API_URL}/message/get_messages`, {
method: 'POST',
headers: {
...
},
body: JSON.stringify({roomId}),
})
.then(response => response.json())
.then(data => {
if (data.type === 'success') {
setMessages(data.data);
}
if (data.error) {
console.log(data.error);
}
})
.catch(error => {
console.log('[ERROR] While fetching messages: ' + error.message);
});
} else {
console.log('token is null');
}
})
.catch(error => {
console.log('[ERROR] While fetching token: ' + error.message);
});
};
export const createMessage = (message, setMessages) => {
AsyncStorage.getItem('token')
.then(token => {
if (token) {
fetch(`${API_URL}/message/create_message`, {
method: 'POST',
headers: {
...
},
body: JSON.stringify(message),
})
.then(response => response.json())
.then(data => {
if (data.type === 'success') {
const latestMessage = data.data;
setMessages((prevMessages) => ([
...prevMessages,
latestMessage,
]));
// socket.emit('send2user', latestMessage);
}
if (data.error) {
console.log(data.error);
}
})
.catch(error => {
console.log('[ERROR] While fetching messages: ' + error.message);
});
} else {
console.log('token is null');
}
})
.catch(error => {
console.log('[ERROR] While fetching token: ' + error.message);
});
};
ChatScreen.js
const ChatScreen = () => {
const {params} = useRoute();
const roomId = params?.roomId;
const navigator = useNavigation();
const {user, rooms} = useAuth();
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const sendMessage = () => {
if (input) {
const message = {
roomId,
senderId: user._id,
text: input,
};
createMessage(message, setMessages);
setInput('');
}
};
useEffect(() => {
fetchMessages(roomId, setMessages);
}, []);
const scrollViewRef = useRef();
return (
<SafeAreaView>
<KeyboardAvoidingView>
<>
{/* RENDER MESSAGES WITH SCROLLVIEW */}
<ScrollView
ref={scrollViewRef}
onContentSizeChange={() =>
scrollViewRef.current.scrollToEnd({animated: true})
}
onLayout={() =>
scrollViewRef.current.scrollToEnd({animated: true})
}>
{messages.length > 0 ? (
messages.map((message, index) => (
<MessageItem key={index} myID={user._id} data={message} />
))
) : (
<Text>Start Chatting</Text>
)}
</ScrollView>
<View>
<View>
<TextInput
value={input}
onChangeText={setInput}
placeholder="Type here"
/>
</View>
<TouchableOpacity
onPress={sendMessage}>
<IonIcon name="ios-add" size={28} color="#fff" />
</TouchableOpacity>
</View>
</>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
export default ChatScreen;
So, as you can see, I cannot make it real time using socket.io and I'm also confused whether to fetch/create messages using socket or api endpoint. If someone could help me in this problem, I would really appreciate it! I just want to make this work like a real time chat app using socketio.
UPDATE:
By watching some tutorials & documentation of socket.io, I tried fetch and post a message and receive real-time update, but again, failed. So, below are the changes that I made:
node server socket.js
const Message = require('../models/Message');
module.exports = (io) => {
io.on('connection', (socket) => {
console.log('A user connected.');
socket.on('get_messages', async (roomId) => {
const messages = await Message.find({roomId});
socket.broadcast.to(roomId).emit('get_messages', messages);
});
socket.on('listener', async (data) => {
// here, I am able to receive `data` : {roomId: '...', senderId: '...', text: '...'}
io.to(data.roomId).emit('listener', data); // but using this, I cannot listen to changes, look in client file below this.
const message = new Message({
roomId: data.roomId,
senderId : data.senderId,
text: data.text,
});
await message.save();
});
});
};
client - react - ChatScreen.js:
import {socket} from '../../utils/utility';
const ChatScreen = () => {
const {params} = useRoute();
const roomId = params?.roomId;
const navigator = useNavigation();
const {user, rooms} = useAuth();
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const InitialFetchMessages = (_roomId) => {
socket.on('get_messages', () => {
socket.emit('get_messages', ); // HOW TO RECEIVE THE MESSAGES HERE?
});
};
const sendMessage = () => {
if (input) {
const message = {
roomId,
senderId: user._id,
text: input,
};
socket.emit('listener', message);
setInput('');
}
};
// main real-time listener
useEffect(() => { // NOT RUNNING
socket.on('listener', (data) => { // data = received data
setMessages([...messages, data]);
});
return () => socket.off('listener');
}, [messages]);
useEffect(() => {
InitialFetchMessages(roomId, setMessages);
}, []);
const scrollViewRef = useRef();
return (
<SafeAreaView>
<KeyboardAvoidingView>
<>
{/* RENDER MESSAGES WITH SCROLLVIEW */}
<ScrollView
ref={scrollViewRef}
onContentSizeChange={() =>
scrollViewRef.current.scrollToEnd({animated: true})
}
onLayout={() =>
scrollViewRef.current.scrollToEnd({animated: true})
}>
{messages.length > 0 ? (
messages.map((message, index) => (
<MessageItem key={index} myID={user._id} data={message} />
))
) : (
<Text>Start Chatting</Text>
)}
</ScrollView>
<View>
<View>
<TextInput
value={input}
onChangeText={setInput}
placeholder="Type here"
/>
</View>
<TouchableOpacity
onPress={sendMessage}>
<IonIcon name="ios-add" size={28} color="#fff" />
</TouchableOpacity>
</View>
</>
</KeyboardAvoidingView>
</SafeAreaView>
);
};
export default ChatScreen;
Can anyone point out what is going on and why it is not emitting to roomId because I want it to be private and based on roomId.
In frontend, you're using API to send a message instead of that use Socket on and emit to be real time. Since you're using API so chat will update only if it's refreshed.
Related
So far i'm stuck on my useEffect that fetches all the current messages and renders the state accordingly. as of right now it doesn't render the new state until page is refreshed.
const Home = ({ user, logout }) => {
const history = useHistory();
const socket = useContext(SocketContext);
const [conversations, setConversations] = useState([]);
const [activeConversation, setActiveConversation] = useState(null);
const classes = useStyles();
const [isLoggedIn, setIsLoggedIn] = useState(false);
const addSearchedUsers = (users) => {
const currentUsers = {};
// make table of current users so we can lookup faster
conversations.forEach((convo) => {
currentUsers[convo.otherUser.id] = true;
});
const newState = [...conversations];
users.forEach((user) => {
// only create a fake convo if we don't already have a convo with this user
if (!currentUsers[user.id]) {
let fakeConvo = { otherUser: user, messages: [] };
newState.push(fakeConvo);
}
});
setConversations(newState);
};
const clearSearchedUsers = () => {
setConversations((prev) => prev.filter((convo) => convo.id));
};
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
const sendMessage = (data, body) => {
socket.emit("new-message", {
message: data.message,
recipientId: body.recipientId,
sender: data.sender,
});
};
const postMessage = async (body) => {
try {
const data = await saveMessage(body);
if (!body.conversationId) {
addNewConvo(body.recipientId, data.message);
} else {
addMessageToConversation(data);
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
const addNewConvo = useCallback(
(recipientId, message) => {
conversations.forEach((convo) => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message);
convo.latestMessageText = message.text;
convo.id = message.conversationId;
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
const addMessageToConversation = useCallback(
(data) => {
// if sender isn't null, that means the message needs to be put in a brand new convo
const { message, sender = null } = data;
if (sender !== null) {
const newConvo = {
id: message.conversationId,
otherUser: sender,
messages: [message],
};
newConvo.latestMessageText = message.text;
setConversations((prev) => [newConvo, ...prev]);
}
conversations.forEach((convo) => {
console.log('hi', message.conversationId)
if (convo.id === message.conversationId) {
const convoCopy = { ...convo }
convoCopy.messages.push(message);
convoCopy.latestMessageText = message.text;
console.log('convo', convoCopy)
} else {
return convo
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
const setActiveChat = useCallback((username) => {
setActiveConversation(username);
}, []);
const addOnlineUser = useCallback((id) => {
setConversations((prev) =>
prev.map((convo) => {
if (convo.otherUser.id === id) {
const convoCopy = { ...convo };
convoCopy.otherUser = { ...convoCopy.otherUser, online: true };
return convoCopy;
} else {
return convo;
}
}),
);
}, []);
const removeOfflineUser = useCallback((id) => {
setConversations((prev) =>
prev.map((convo) => {
if (convo.otherUser.id === id) {
const convoCopy = { ...convo };
convoCopy.otherUser = { ...convoCopy.otherUser, online: false };
return convoCopy;
} else {
return convo;
}
}),
);
}, []);
// Lifecycle
useEffect(() => {
// Socket init
socket.on("add-online-user", addOnlineUser);
socket.on("remove-offline-user", removeOfflineUser);
socket.on("new-message", addMessageToConversation);
return () => {
// before the component is destroyed
// unbind all event handlers used in this component
socket.off("add-online-user", addOnlineUser);
socket.off("remove-offline-user", removeOfflineUser);
socket.off("new-message", addMessageToConversation);
};
}, [addMessageToConversation, addOnlineUser, removeOfflineUser, socket]);
useEffect(() => {
// when fetching, prevent redirect
if (user?.isFetching) return;
if (user && user.id) {
setIsLoggedIn(true);
} else {
// If we were previously logged in, redirect to login instead of register
if (isLoggedIn) history.push("/login");
else history.push("/register");
}
}, [user, history, isLoggedIn]);
useEffect(() => {
const fetchConversations = async () => {
try {
const { data } = await axios.get("/api/conversations");
setConversations(data);
} catch (error) {
console.error(error);
}
};
if (!user.isFetching) {
fetchConversations();
}
}, [user]);
const handleLogout = async () => {
if (user && user.id) {
await logout(user.id);
}
};
return (
<>
<Button onClick={handleLogout}>Logout</Button>
<Grid container component="main" className={classes.root}>
<CssBaseline />
<SidebarContainer
conversations={conversations}
user={user}
clearSearchedUsers={clearSearchedUsers}
addSearchedUsers={addSearchedUsers}
setActiveChat={setActiveChat}
/>
<ActiveChat
activeConversation={activeConversation}
conversations={conversations}
user={user}
postMessage={postMessage}
/>
</Grid>
</>
);
};
this is the main part im working on, the project had starter code when i began and was told not to touch the backend so i know its something wrong with the front end code. i feel like im missing something important for the socket.io
import { io } from 'socket.io-client';
import React from 'react';
export const socket = io(window.location.origin);
socket.on('connect', () => {
console.log('connected to server');
});
export const SocketContext = React.createContext();
this is how i have the socket.io setup, if anyone could point me in the right direction that would be cool. I have been reading up on socket.io as much as I can but am still pretty lost on it.
Based on the assumption the backend is working properly...
const addNewConvo = useCallback(
(recipientId, message) => {
conversations.forEach((convo) => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message);
convo.latestMessageText = message.text;
convo.id = message.conversationId;
}
});
setConversations(conversations);
},
[setConversations, conversations],
);
setConversations(conversations);
This is an incorrect way to set a state using the state's variable, and such it wont do anything. Likely why your code wont change until refresh.
Suggested fix:
const addNewConvo = useCallback(
(recipientId, message) => {
setConversations(previousState => previousState.map(convo => {
if (convo.otherUser.id === recipientId) {
convo.messages.push(message)
convo.latestMessageText = message.text;
convo.id = message.conversationId;
return convo
}
return convo
}))
},
[setConversations, conversations],
);
note: even above could be done more efficiently since I made a deep copy of messages
Hello, I was wondering if anybody have the following issue(Unhandled Rejection (TypeError): info.contract.methods is undefined):
enter image description here
Here is where info gets setup
const intialInfo = {
connected: false,
status: null,
account: null,
contract: null,
};
const intialDropState = {
loading: false,
list:[],
};
console.log(contract);
const DropList = () => {
const [info, setInfo] = useState(intialInfo);
const [drops,setDrops] = useState(intialDropState);
**My smart contract calls getDrops by doing the following:
//Get the NFT drop objects list
function getDrops() public view returns(Drop[] memory) {
return drops;
}
**
here is the part of the code that has the issue
const getDrops = async() => {
setDrops(prevState => ({
...prevState,
loading: true,
}));
info.contract.methods
.getDrops()
.call()
.then((res) => {
console.log(res);
setDrops({
loading: false,
list: res,
});
})
.catch((err) => {
console.log(err);
setDrops(intialDropState);
});
};
Here is the code
I have taken a picture of all my code in the following photos:
enter image description here
enter image description here
import contract from "../contract/contract.json";
import Web3 from "web3";
import {useState, useEffect} from "react";
const intialInfo = {
connected: false,
status: null,
account: null,
contract: null,
};
const intialDropState = {
loading: false,
list:[],
};
console.log(contract);
const DropList = () => {
const [info, setInfo] = useState(intialInfo);
const [drops,setDrops] = useState(intialDropState);
// connecting to metamask and inital state of web dapp
const init = async() => {
//Connect to blockchain to metamask if there is a metamask
if(window.ethereum.isMetaMask){
const accounts = await window.ethereum.request({
method: "eth_requestAccounts",
});
const networkId = await window.ethereum.request({
method: "net_version",
});
//network == 4 for testnet for ETH use networkId == 1
if(networkId === 4){
let web3 = new Web3(window.ethereum);
setInfo({
...intialInfo,
connected: true,
account: accounts[0],
contract: new web3.ethereum.Contract(contract.abi, contract.address),
});
}
else{
setInfo({ ...intialInfo, status: "You need to be on the Ethereum testnet."});
}
}
else{
setInfo({ ...intialInfo, status: "You need metamask."});
}
};
const initOnChange = () => {
if(window.ethereum){
window.ethereum.on("accountsChanged", () =>{
window.location.reload();
});
window.ethereum.on("chainChanged", () =>{
window.location.reload();
});
}
};
const getDrops = async() => {
setDrops(prevState => ({
...prevState,
loading: true,
}));
info.contract.methods
.getDrops()
.call()
.then((result) => {
console.log(result);
setDrops({
loading: false,
list: result,
});
})
.catch((err) => {
console.log(err);
setDrops(intialDropState);
});
};
useEffect(() => {
init();
initOnChange();
}, []);
return (
<div>
<button onClick={() => getDrops()}>Get Drops </button>
{drops.loading ? <p>Loading</p> : null}
</div>
);
};
export default DropList;
I think the issue is with the useEffect
useEffect(() => {
init();
initOnChange();
}, []);
You have to pass dependencies,
useEffect(() => {
init();
initOnChange();
// as far as I see, those are the only dependencies
}, [window.ethereum,info]);
then in jsx to avoid your app crashing:
info && info.contract && info.contract.methods
or depending on your packages, you could use this
info?.contract?.methods
I have been getting this error and I tried all I can To solve it but none. I feel the User is not being fetched from Users.js file. here is Index.js file
const socketio = require("socket.io");
const router = require("./router");
const http = require("http");
const cors = require("cors");
const port = process.env.PORT || 5000;
const app = express();
const server = http.createServer(app);
const { addUser, removeUser, getUser, getUserInRoom } = require("./users");
const io = socketio(server);
app.use(cors());
// app.use(router);
io.on("connection", (socket) => {
console.log("We have a new connection!");
socket.on("join", ({ name, room }, callback) => {
const { user } = addUser({ id: socket.id, name, room });
console.log(user);
// if (error) return callback(error);
socket.emit("message", {
user: "admin",
text: `${user.name}, welcome to the room ${user.room}.`,
});
socket.broadcast
.to(user.room)
.emit("message", { user: "admin", text: `${user.name} has joined` });
socket.join(user.room);
callback();
});
socket.on("sendMessage", (message, callback) => {
const user = getUser(socket.id);
console.log(user);
io.to(user.room).emit("message", { user: user.name, text: message });
callback();
});
socket.on("disconnect", () => {
console.log("User had just left!");
});
});
server.listen(port, () => console.log(`Server has started on port ${port}`));
Here is My Users.js file
const users = [];
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase();
room = room.trim().toLowerCase();
const existingUser = users.find((user) => {
user.room === room && user.name === name;
});
if (existingUser) {
return { error: "Username is taken" };
}
const user = { id, name, room };
users.push(user);
return { user };
};
const removeUser = (id) => {
const index = users.findIndex((user) => {
user.id === id;
});
if (index !== -1) {
return users.splice(index, 1)[0];
}
};
const getUser = (id) => {
users.find((user) => user.id === id);
};
const getUserInRoom = (room) => {
users.filter((user) => user.room === room);
};
module.exports = { addUser, removeUser, getUser, getUserInRoom };
I have searched through for any possible misplaced variable but i found none.
And Lastly, My client side
import "./Chat.css";
import queryString from "query-string";
import io from "socket.io-client";
import { InfoBar } from "../InfoBar/InfoBar";
import { Input } from "../Input/Input";
import { Messages } from "../Messages/Messages";
let socket;
export const Chat = ({ location }) => {
const [name, setName] = useState("");
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const [room, setRoom] = useState("");
const ENDPOINT = "localhost:5000";
// ("ws://localhost:5000", { transports: ["websocket", "polling"] });
useEffect(() => {
socket = io(ENDPOINT);
const { name, room } = queryString.parse(location.search);
// console.log(name, room);
setName(name);
setRoom(room);
// console.log(socket);
socket.emit("join", { name, room }, () => {});
return () => {
socket.emit("disconnect");
socket.off();
};
}, [ENDPOINT, location.search]);
//UseEffect for the messages
useEffect(() => {
socket.on("message", (message) => {
setMessages(messages=>[...messages, message]);
});
}, []);
const sendMessage = (event) => {
event.preventDefault();
if (message) {
socket.emit("sendMessage", message, () => setMessage(""));
}
};
console.log(message, messages);
return (
<div className="outerContainer">
<div className="container">
<InfoBar room={room} />
<Input
setMessage={setMessage}
message={message}
sendMessage={sendMessage}
/>
<Messages messages={messages} />
{/* <input
value={message}
onChange={(event) => setMessage(event.target.value)}
onKeyPress={(event) =>
event.key === "Enter" ? sendMessage(event) : null
}
/> */}
</div>
</div>
);
};
I will appreciate every available help
You don't have a variable named user, thus user is undefined and you can't get a property of an undefined object.
const { user } = addUser({ id: socket.id, name, room });
does not create a user variable, but an unnamed object, which has a property named user.
I don't know, why your addUser function does not just return the created user object but encapsulates it in an object. Maybe you could just do
const addUser = ({ id, name, room }) => {
...
const user = { id, name, room };
users.push(user);
return user;
}
and then
const user = addUser(....)
If that is not possible and the return type of addUser can't be changed, you could do the following
const user = addUser(...).user;
socket.emit("message", {
user: "admin",
text: `${user.name}, welcome to the room ${user.room}.`,
});
You do not really need the {} around the return
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase();
room = room.trim().toLowerCase();
const existingUser = users.find((user) => {
user.room === room && user.name === name;
});
if (existingUser) {
return { error: "Username is taken" };
}
const user = { id, name, room };
users.push(user);
return { user };
};
Instead do it like this:
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase();
room = room.trim().toLowerCase();
const existingUser = users.find((user) => {
user.room === room && user.name === name;
});
if (existingUser) {
return { error: "Username is taken" };
}
const user = { id, name, room };
users.push(user);
return user;
};
There is a two different component. One is patient and another is Doctor. The doctor can be on any page. When patient fires join event from his page to a particular doctor(e.g abc), the node server will then catch that event and will send JoinAccept event to 'abc doctor'. Following doctor should get notification that following patient wants to connect with you.
To achieve this, I could able to send 'Join' event up to server but could not able to listen JoinAccept event on doctor's end.
This is what I have done
context.js
import io from "socket.io-client";
import { SOCKET_URL } from "constants/url";
const SocketContext = React.createContext();
const SocketProvider = ({ children }) => {
const [socketClient, setSocketClient] = React.useState();
const [socketUpdated, setSocketUpdated] = React.useState(false);
React.useEffect(() => {
const socket = io(SOCKET_URL);
setSocketClient(socket);
return () => {
io.disconnect();
};
}, []);
React.useEffect(() => {
console.log("socketClient", socketClient);
if (socketClient) {
const tokenData =
!!localStorage.token &&
JSON.parse(atob(localStorage.token.split(".")[1]));
if (tokenData.user) {
console.log("user", tokenData.user);
socketClient.emit("clientData", tokenData.user);
socketClient.on("connected", msg => {
console.log("connected");
setSocketUpdated(true);
});
// setSocketUpdated(true);
}
}
}, [socketClient]);
console.log("socket updated", socketUpdated);
return (
<>
<SocketContext.Provider value={{ socket: socketClient, socketUpdated }}>
{children}
</SocketContext.Provider>
</>
);
};
export { SocketContext, SocketProvider };
Patient.js (it will fire Join event)
const Patient = () => {
const { socket } = React.useContext(SocketContext);
React.useEffect(() => {
const data = {
to: "abcdoctor#gmail.com",
from: "patient#gmail.com",
message: "Join a call"
};
socket.emit("Join", data);
}, [socket]);
return (
<div>
<h1>Patient</h1>
</div>
);
};
Doctor.js
const DoctorParentComponent = () => {
return (
<>
<SocketProvider>
<h1>Navbar</h1>
<DoctorRoutes />
</SocketProvider>
</>
);
};
DoctorPage.js
const DoctorPage = () => {
const [msg, setMessage] = React.useState("");
const { socket, socketUpdated } = React.useContext(SocketContext);
console.log("Doctor socket", socket, socketUpdated);
React.useEffect(() => {
console.log("socket", socket);
if (socket !== undefined) {
console.log("socket join", socket);
socket.on("JoinAccept", message => {
debugger;
console.log("message", message);
setMessage(message);
});
}
}, [socket]);
return (
<div>
<h1>Doctor </h1>
</div>
);
};
server.js
io.of("/sockets").on("connection", socket => {
console.log("socket connection is made!!!", socket.id);
socket.on("clientData", clientData => {
console.log(clientData, "CLEITN DATA");
socket.emit("connected", "connected");
});
socket.on("Join", data => {
const msg = {
message: "I am joining"
};
console.log("socket", socket.id);
console.log("I am Joining", data);
socket.broadcast.to(data.to).emit("JoinAccept", msg);
});
}
In my case the server gets Join event from patients and then sends event to requested doctor but doctor page is unresponsive. I mean doctor page does not listens the changes i.e it could not listens socket event JoinAccept so that it can join patient's request.
UPDATE
changing
socket.on("Join", data => {
const msg = {
message: "I am joining"
};
console.log("socket", socket.id);
console.log("I am Joining", data);
socket.broadcast.to(data.to).emit("JoinAccept", msg);
});
to following works
socket.on("Join", data => {
const msg = {
message: "I am joining"
};
console.log("socket", socket.id);
console.log("I am Joining", data);
io.of("/sockets").emit("JoinAccept", msg)
});
But I want to emit 'JoinAccept' event only for a particular doctor which patient has requested for from join events.
On DoctorPage.js socket is an object, if you put this object as the only variable in the dependency array it's going to run as a loop.
Try switching to the socket id instead, so:
React.useEffect(() => {
console.log("socket", socket);
if (socket !== undefined) {
console.log("socket join", socket);
socket.on("JoinAccept", message => {
debugger;
console.log("message", message);
setMessage(message);
});
}
}, [socket.id]);
How can I utilize a user's Firebase ID ("id" : "1234567890") as a key for a separate data set within the same db?
the following would be my login detail where I would pull the ID from:
"users": {
"1234567890abcdefghijklmnopqrstuvwxyz" : {
"dp" : "https://www.profilePic.com",
"first_name" : "John",
"id" : "1234567890",
"last_name" : "Doe",
"token" : "abcdefghijklmnopqrstuvwxyz1234567890",
"uid" : "987654321"
}
}
Within Firebase Functions I currently have my code as:
admin.database().ref('location_config/{id}').set({current_location:[34.047220, -118.443534]})
The result currently comes out as:
"location_config": {
"{id}": {
"current_location": [34.047220, -118.443534]
}
}
But this is how I would like the data to appear with the ID being the key:
"location_config": {
"1234567890": {
"current_location": [34.047220, -118.443534]
}
}
The screenshot below illustrates how the UID is dynamic while the ID is constant.
Here is the code within Firebase:
let fbLocation;
module.exports = (event) => {
event.geoFire = functions.database.ref('users').onUpdate(event => {
admin.database().ref('/portal_coordinates_all').once('value', snapshot =>{
fbLocation = snapshot.val();
console.log ("snapshot", fbLocation);
}).then(() => {
// Create a Firebase reference where GeoFire will store its information
let firebaseRef = admin.database().ref('geofire');
// Create a GeoFire index
let geoFire = new GeoFire(firebaseRef);
geoFire.set(fbLocation)
.then(() => {
console.log("Provided key has been added to GeoFire");
}).catch(err => console.log(err))
.then(() => {
let geoQuery = geoFire.query({
center: [34.047220, -118.443534],
radius: 2
});
let locations = [];
let onKeyEnteredRegistration = geoQuery.on("key_entered", function(key, location, distance) {
locations.push(location);
});
// fires once when this query's initial state has been loaded from the server.
let onReadyRegistration = geoQuery.on("ready", function() {
console.log("GeoQuery has loaded and fired all other events for initial data");
console.log(locations);
// ******* here is where I'm having the issue *******
admin.database().ref( 'location_config/`${id}`' ).set( {current_location: locations} )
// **************************************************
// Cancel the "key_entered" callback
onKeyEnteredRegistration.cancel();
});
}).catch(err => console.log(err))
})
})
}
And here is the code within React Native:
import React, { Component } from 'react';
import {
StyleSheet,
View,
ActivityIndicator,
Button
} from 'react-native';
import firebase from 'firebase';
import { connect } from 'react-redux';
import { loginSuccess } from '../actions/AuthActions';
const FBSDK = require('react-native-fbsdk');
const { LoginManager, AccessToken } = FBSDK;
class Login extends Component {
constructor(props) {
super(props);
this.state = {
showSpinner: true,
};
}
componentDidMount() {
this.fireBaseListener = firebase.auth().onAuthStateChanged(auth => {
if (auth) {
this.firebaseRef = firebase.database().ref('users');
this.firebaseRef.child(auth.uid).on('value', snap => {
const user = snap.val();
if (user != null) {
this.firebaseRef.child(auth.uid).off('value');
this.props.loginSuccess(user);
}
});
} else {
this.setState({ showSpinner: false });
}
});
}
onPressLogin() {
this.setState({ showSpinner: true })
LoginManager.logInWithReadPermissions([
'public_profile',
'user_birthday',
'email',
'user_photos'
])
.then((result) => this.handleCallBack(result),
function(error) {
alert('Login fail with error: ' + error);
}
);
}
handleCallBack(result) {
let that = this;
if (result.isCancelled) {
alert('Login canceled');
} else {
AccessToken.getCurrentAccessToken().then(
(data) => {
const token = data.accessToken
fetch('https://graph.facebook.com/v2.8/me? fields=id,first_name,last_name&access_token=' + token)
.then((response) => response.json())
.then((json) => {
const imageSize = 120
const facebookID = json.id
const fbImage = `https://graph.facebook.com/${facebookID}/picture?height=${imageSize}`
this.authenticate(data.accessToken)
.then(function(result) {
const { uid } = result;
that.createUser(uid, json, token, fbImage)
});
})
.catch(function(err) {
console.log(err);
});
}
);
}
}
authenticate = (token) => {
const provider = firebase.auth.FacebookAuthProvider;
const credential = provider.credential(token);
return firebase.auth().signInWithCredential(credential);
}
createUser = (uid, userData, token, dp) => {
const defaults = {
uid,
token,
dp
};
firebase.database().ref('users').child(uid).update({ ...userData, ...defaults });
}
render() {
return (
this.state.showSpinner ? <View style={styles.container}><ActivityIndicator animating={this.state.showSpinner} /></View> :
<View style={styles.container}>
<Button
onPress={this.onPressLogin.bind(this)}
title="Login with Facebook"
color="#841584"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
}
});
const mapStateToProps = (state) => {
console.log('mapStateToProps', state);
return {
logged: state.auth.loggedIn,
user: state.auth.user
};
};
export default connect(mapStateToProps, { loginSuccess })(Login);
After banging my head against the wall as to why I couldn't grab the Facebook ID, it turned out that there isn't a need to grab this ID since Firebase's UID stays constant. I was unaware that the Firebase ID didn't change because in my test environment I would always login resulting in a new UID being created.