React Native: How to access a variable from parent component in child component? - javascript

i am trying to pass my newsocket variable from my MessagesScreen.js to my ChatScreen.js.
I have been stuck on this point for a while and would appreciate any help possible. What i am trying to achieve is that only one connection gets emitted which i can listen to events on both screen.
The connection is now opened on the messagesScreen. My problem now is if user 1 is on the allmessages screen and user 2 is inside the chat. And user 2 sends user 1 a message, user 1's screen does not automatically update with the last message for the conversation the message was sent to, I need to either scroll to refresh or navigate from one page to the other in order for it to appear.
Here is my code:
PARENT --> messagesScreen.js
function MessagesScreen({navigation}) {
const [posts, setPosts] = useState([]);
const { user } = useAuth();
const [socket, setSocket] = useState(null);
const loadposts = async () => {
const response = await messagesApi.getMessages();// here i am loading all the conversation this user has
setPosts(response.data)
};
useEffect(() => {
newsocket = sockets(user.id); // newsocket is what i am trying to pass to child component
setSocket(newsocket);
loadPosts()
newsocket.on("send_message", (msg) => {
console.log("messages:", msg);
})
}, []);
return (
<FlatList
data={posts}
keyExtractor={(post) => post.id.toString()}
renderItem={({ item,index }) => (
<MessagesList
title={item.Post.title}
subTitle={item.Messages[0].message}
onPress={() => navigation.navigate(
routes.CHAT,{message:item,index,newsocket:socket})}
/>
)}
/>
)
CHILD ---> chatScreen.js
function ChatScreen({route,navigation,socket}) {
const [messages, setMessages] = useState([]);
const { user } = useAuth();
const index = route.params.index;
const message = route.params.message;
const newsocket = route.params.newsocket;
const loadListings = async () => {
const response = await messagesApi.getConversation(message.id);// here i am loading the messages in that specific conversation
setMessages(response.data.Messages)
};
useEffect(() => {
loadListings()
newsocket.emit('subscribe', message.id);
newsocket.on("send_message", (msg) => {
console.log("this is the chat messages:", msg);
setMessages(messages => [msg, ...messages]);
});
}, []);
const onSend = (ConversationId,senderId,receiverId,message) => {
const to = (user.id===route.params.message.user1?
route.params.message.user2:route.params.message.user1)
socket.emit('message', { to: to, from: user.id, message,ConversationId});
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
};
return(
<FlatList
inverted
data={messages}
keyExtractor={(item,index)=>index.toString()}
extraData={messages} // add this
renderItem={({item,index})=>(
<MessageBubble
text={item.message}
mine={item.senderId !== user.id}
/>
)}
/>
)
socket.js
import io from 'socket.io-client';
const newsocket = (user) => {
let newsocket = io.connect("http://192.168.1.107:9000")
newsocket.on('connect', msg => {
console.log(`waiting for user: ${user} to join a conversation`)
});
newsocket.emit('waiting', user);
return newsocket;
}
export default newsocket;

I would approach this differently.
You can create your socket connection as a shared service in a separate module and simply import that into the relevant components you need. In this shared module you handle connecting/disconnecting and return an existing connection or create a new connection to return.
Quick rough:
// socket-server.ts
import io from 'socket.io-client';
let socket: SocketIOClient.Socket = null;
export const getSocketServer = (): Promise<SocketIOClient.Socket> => {
return new Promise<SocketIOClient.Socket>(resolve => {
if (socket) {
console.info(`returning existing socket: ${socket.id}`);
return resolve(socket);
}
socket = io('http://localhost:4000', {
autoConnect: false,
});
socket.on('connect_error', (err) => {
console.error(err);
})
socket.on('connect', () => {
console.info(`creating new socket: ${socket.id}`);
return resolve(socket);
});
socket.open();
})
}
// then in your relevant modules
// module-a.ts
import React, {useEffect, useState} from 'react';
import {getSocketServer} from './../components/socket-server';
const Component = () => {
useEffect(() => {
const connect = async () => {
const socket = await getSocketServer();
socket.on('hello', (message) => {
console.info('hello from module A', message);
});
}
connect();
}, []);
return (
<>
<h2>Module A</h2>
</>
)
}
export default Component;
You could maybe also look at creating a Context Provider and share the socket with relevant modules as needed.
Context provides a way to pass data through the component tree without
having to pass props down manually at every level.

On the MessagesScreen screen you are passing the SOCKET function and not the variable it self . i think you do not need the function . you directly pass the variable and access in chatScreen screen .
MessagesScreen.js
routes.CHAT,{message:item,index,updateView, newsocket})}
chatScreen.js
const newsocket = route.params.newsocket;
....
newsocket.emit('subscribe', message.id); // call like this

Related

How to reconnect socket instance from Socket.io after page refresh on React page?

Trying to make a simple chat app with express + socket.io and react but this issue has been racking my brain. Whenever I refresh the page on messaging app route, I can't send any messages. I tried circumventing that by using react-router with the history API to send me back to homepage. However, when I'm routed back to the homepage, I can't re-enter into the chat page unless I refresh again on the homepage. It seems that the socket object is null whenever I refresh the page.
Server Code:
const room = "general";
const room2 = "memes";
const LOGIN = "loginEvent";
const NEW_MESSAGE_EVENT = "newMessageEvent";
const LEAVE_EVENT = "leaveEvent";
io.on("connection", (socket) => {
socket.on(LOGIN, ({ nickname, room }, callback) => {
const user = addUser(socket.id, nickname, room);
socket.join(user.room);
callback();
// socket.emit()
});
socket.on(NEW_MESSAGE_EVENT, (data) => {
console.log(data);
io.in(room).emit(NEW_MESSAGE_EVENT, data);
});
socket.on("disconnect", () => {
const deletedUser = delUser(socket.id);
console.log("User deleted:", deletedUser);
if (deletedUser) {
io.in(room).emit(LEAVE_EVENT, {
notification: `User ${deletedUser.nickname} has just left`,
});
}
socket.leave(room);
});
});
In my client-side, I use the react context API to store the socket object like so:
import React, { createContext } from "react";
import io from "socket.io-client";
const SocketContext = createContext();
const SocketProvider = ({ children }) => {
const ENDPOINT = "http://localhost:3030";
const socket = io(ENDPOINT);
return (
<SocketContext.Provider value={socket}>
{children}
</SocketContext.Provider>
);
};
export { SocketContext, SocketProvider };
Then, I create a hook to send messages:
import { useState, useContext, useEffect } from "react";
import { SocketContext } from "../contexts/socketContext";
const NEW_MESSAGE_EVENT = "newMessageEvent";
const useRoom = () => {
const socket = useContext(SocketContext);
const [messages, setMessages] = useState([]);
useEffect(() => {
socket.on(NEW_MESSAGE_EVENT, (message) => {
const incomingMessage = {
...message,
isOwner: message.id === socket.id,
};
setMessages((messages) => [...messages, incomingMessage]);
});
return () => {
socket.close();
};
}, [socket]);
const sendMessage = (message) => {
socket.emit(NEW_MESSAGE_EVENT, { body: message, id: socket.id });
};
return { messages, sendMessage };
};
export default useRoom;

useEffect runs twice as much for each message sent

I am building a simple chat app with react, express and socket.io
I got stuck on receiving message from backend server.
Every time user receive some message, the useEffect will runs approximately twice as much as before so after 5 or 6 received messages the app start really slow down.
useEffect(() => {
socket.on('mes', (data) => {
setChat([...chat, data]);
});
}, [chat]);
any idea how to make it run just once every time user receive a message?
Whole code
import Chat from '../Chat/chat';
import queryString from 'query-string';
let socket;
const ChatRoom = ({ location }) => {
const [name, setName] = useState('');
const [room, setRoom] = useState('');
const [message, setMessage] = useState('');
const [chat, setChat] = useState([]);
const ENDPOINT = 'http://localhost:4001/';
useEffect(() => {
const { name, room } = queryString.parse(location.search);
setName(name);
setRoom(room);
socket = socketIOClient(ENDPOINT);
socket.emit('join', { name, room });
return () => {
socket.emit('disconnect');
socket.disconnect();
};
}, [ENDPOINT, location.search]);
const click = (e) => {
e.preventDefault();
socket.emit('message', message);
setMessage('');
};
useEffect(() => {
socket.on('mes', (data) => {
setChat([...chat, data]);
});
}, [chat]);
return (
<div className="ChatRoom-Container">
{chat.map((mes, index) => {
return <Chat text={mes.text} user={mes.user} key={index}></Chat>;
})}
<input
value={message}
className="ChatRoom-Input"
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(e) => {
return e.key === 'Enter' ? click(e) : null;
}}
></input>
</div>
);
};
export default ChatRoom;
Use setChat(prev => next) instead of setChat(value) so you don't have to reference the previous value from the closure:
useEffect(() => {
socket.on('mes', (data) => {
setChat(prev => [...prev, data]);
});
}, []);
useEffect(() => {
socket.on('mes', (data) => {
setChat([...chat, data]);
});
}, [chat]);
Here in your dependency array you have the chat variable as a dependency.
According to rules when the value of chat changes the effect runs.
as a result after you call setChat() one time it changes the value of chat and as a result it runs the effect one more time. that's why your effect is being called twice.

React hooks array passing in number when passed into props of component

I am currently working on a chat application and for some reason every time I pass in my array of messages as a prop to another component it passes in a number to the component instead of the message object. I have tried a lot of different methods of passing it in regarding using multiple components etc but it seems to still be passing in the number of elements for some reason. Any help is appreciated... code is below
Component receiving the props
import React, { useEffect } from 'react'
import Message from '../../Message/Message'
function Messages({ messages }) {
useEffect(() => {
console.log(messages)
}, [messages])
return (
<div>
test
</div>
)
}
export default Messages
// Import React dependencies.
import React, { useEffect, useState, } from "react";
// Import React dependencies.
import io from 'socket.io-client'
import axios from 'axios'
import Messages from './Messages/Messages'
import uuid from 'react-uuid'
import { Redirect } from 'react-router-dom'
// Import the Slate components and React plugin.
const ENDPOINT = 'http://localhost:5000/'
export const socket = io.connect(ENDPOINT)
const LiveChatFunction = ({ group_id }) => {
// Add the initial value when setting up our state.
const [message, setValue] = useState("")
const [user, setUser] = useState("")
const [groupId, setGroup] = useState('')
const [messages, setMessages] = useState([])
const [toLogin, userAuth] = useState(false)
useEffect(() => {
setGroup(group_id)
axios.post('http://localhost:5000/api/users/refresh_token', null, { withCredentials: true }).then(data => {
if (!data.data.accessToken) {
userAuth(true)
}
})
axios.get('http://localhost:5000/api/users/userInfo', { withCredentials: true }).then(data => {
setUser(data.data.user)
})
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
axios.get(`http://localhost:5000/live/${group_id}`).then(x => {
console.log(x.data)
})
}, [group_id, messages])
function setClick() {
const data = {
messageId: uuid(),
user,
groupId,
message
}
socket.emit('message', data)
}
if (toLogin) {
return (
<Redirect to="/login" />
)
}
return (
<div>
<input placeholder="message" type="text" onChange={value => {
setValue(value.target.value)
socket.emit('typing-message', { username: user, time: new Date() })
}} />
<button onClick={setClick}>Submit</button>
<Messages messages={messages} />
</div>
)
}
export default LiveChatFunction;
I have added some comments of what I think you can change:
useEffect(() => {
const recieveFunction = (data) => {
//using callback so no dependency on messages
setMessages((messages) => messages.push(data));
};
async function init() {
//next line is pointless, this runs when group_id
// has changed so something must have set it
// setGroup(group_id);
await axios //not sure if this should be done before listening to socket
.post(
'http://localhost:5000/api/users/refresh_token',
null,
{ withCredentials: true }
)
.then((data) => {
if (!data.data.accessToken) {
userAuth(true);
}
});
await axios
.get('http://localhost:5000/api/users/userInfo', {
withCredentials: true,
})
.then((data) => {
setUser(data.data.user);
});
//start listening to socket after user info is set
socket.on(`message-${group_id}`, recieveFunction);
axios
.get(`http://localhost:5000/live/${group_id}`)
.then((x) => {
console.log(x.data);
});
}
init();
//returning cleanup function, guessing socket.off exists
return () =>
socket.off(`message-${group_id}`, recieveFunction);
}, [group_id]); //no messages dependencies
console.log('messages are now:',messages);
If messages is still not set correctly then can you log it
So I think I found your problem:
In your useEffect hook, you're setting messages to the wrong thing.
socket.on(`message-${group_id}`, data => {
setMessages(messages.push(data))
});
An example:
const m = [].push();
console.log(m);
// m === 0
const n = [].push({});
console.log(n);
// n === 1
As you can see this is the index.
So what you need is:
socket.on(`message-${group_id}`, data => {
messages.push(data);
setMessages(messages);
});
This will set messages to the array of messages.

SocketIO & ReactJs: Socket Emitting multiple times

My client is responding multiple times when my server emits an event.
By reading other available SOF answers, it seems like I must have duplicated the event listeners somewhere in my code.
I actually have other socket listeners in the same file, and they all (?) seem to be functioning fine.
I've tried moving the listener out of the useEffect to see if it works, but it has the same issue.
I've tried to limit the listener to socket.once, however, there is an additional issue -
user is not defined on the first emit.
Only the second emit then user is defined...
Client:
const getUserInfo= async () => {
try {
const { data } = await axios.get('http://localhost:8080/user/info', axiosConfig);
return data;
} catch (error) {
return { error };
}
}
const App = ({ socket, clientId }) => {
const [state, setState] = useState({
user: undefined,
});
const checkAuthentication = async () => {
const user= await getUserInfo();
localStorage.setItem('user.email', user.email);
setState({ ...state, user});
return true;
}
useEffect(() => {
const onMount = async () => {
const authed = await checkAuthentication()
};
onMount();
}, []);
userEffect(() => {
socket.on("allInfo", async ({ requester }) => { //This catches multiple times
console.log(`Admin ${requester} requested for tablet information.`)
const user= { ...state.user};
socket.emit('infoPayload', { user); //thus this emits multiple times.
})
}, [state.user])
return (
<>
<Route exact path="/"
render={() => <div>{state.user&& state.user.email}</div>}
/>
</>
);
}
export default (App);

Right way to fetch data with react using socket.io

I didn't found any examples about how to fetch data from express server using react with socket.io.
Now i do something like this:
Server.js
io.on('connection', socket => {
console.log(socket.id)
socket.on('disconnect', () => {
console.log(socket.id + ' disconnected')
})
socket.on('load settings', () => {
socket.emit('settings is here', data)
})
})
React.js
const [socket] = useState(io())
const [settings, setSettings] = useState(false)
useEffect(() => {
try {
socket.emit('load settings');
socket.on('settings is here', (data) => {
// we get settings data and can do something with it
setSettings(data)
})
} catch (error) {
console.log(error)
}
}, [])
This looks fine, but there are some things you can improve on, such as disconnecting the socket before unmounting and not making the socket part of state (refer to the code example below).
If you're confused over how to port existing code to hooks, write out the component using classes first, then port part by part to hooks. You could refer to this StackOverflow answer as a cheatsheet.
Using traditional classes, using socket.io looks like:
class App extends React.Component {
constructor(props) {
super(props);
this.socket = io();
}
componentDidMount() {
this.socket.open();
this.socket.emit('load settings');
this.socket.on('settings is here', (data) => {
// we get settings data and can do something with it
this.setState({
settings: data,
})
});
}
componentWillUnmount() {
this.socket.close();
}
render() {
...
}
}
Then you can port the this.socket to use useRef (it doesn't need to be part of state as your render() function doesn't need it. So useRef is a better alternative (although useState is likely to still work).
Port componentDidMount() via using useEffect and passing an empty array as the second argument to make the effect callback only run on mount.
Port componentWillUnmount() via returning a callback function in the useEffect callback which React will call before unmounting.
function App() {
const socketRef = useRef(null);
const [settings, setSettings] = useState(false);
useEffect(() => {
if (socketRef.current == null) {
socketRef.current = io();
}
const {current: socket} = socketRef;
try {
socket.open();
socket.emit('load settings');
socket.on('settings is here', (data) => {
// we get settings data and can do something with it
setSettings(data);
})
} catch (error) {
console.log(error);
}
// Return a callback to be run before unmount-ing.
return () => {
socket.close();
};
}, []); // Pass in an empty array to only run on mount.
return ...;
}
The accepted answer has the downside, that the initial state of the useRef() gets called on every re-render. With a text input for example, a new connection is established on every input change. I came up with two solutions:
Define the socket in the useEffect
const ChatInput = () => {
const [chatMessage, setChatMessage] = useState<string>('');
const socket = useRef<Socket>();
useEffect(() => {
socket.current = io('my api');
socket.current.on('chat message', (message: string) => {
setChatMessage(message);
});
return () => { socket.current?.disconnect(); };
}, []);
const inputHandler = (text: string) => {
socket.current?.emit('chat message', text);
};
return (
<View>
<Text>{chatMessage}</Text>
<TextInput onChangeText={(text) => inputHandler(text)} />
</View>
);
};
Define the socket.io in a useState()
const ChatInput = () => {
const [chatMessage, setChatMessage] = useState<string>('');
const [socket] = useState(() => io('my api'));
useEffect(() => {
socket.on('chat message', (message: string) => {
setChatMessage(message);
});
return () => { socket.disconnect(); };
}, []);
const inputHandler = (text: string) => {
socket.emit('chat message', text);
};
return (
<View>
<Text>{chatMessage}</Text>
<TextInput onChangeText={(text) => inputHandler(text)}/>
</View>
);
};
export default ChatInput;

Categories

Resources