How to set socket response to react state - javascript

Whenever socket receive new data i am trying to set this data to state.
I am creating simple chat application and i want to set this recent chat data received from socket set to state.
var socket = constants.SERVER;
var chatData = ""
class Chat extends Component {
constructor(props) {
super(props);
this.state = { recentChat:[] }
}
componentDidUpdate()
{
socket.on(constants.SOCKET_GET_CHAT, function(data){
console.log(JSON.stringify(data))
this.setState({
recentChat:data
})
});
}
render() {
return(<div/>)
}
}

componentWillMount() {
socket.on(constants.SOCKET_GET_CHAT, (data) => {
this.setState({
recentChat:data
})
});
}
You have to attach listener in componentWillMount or didMount.

socket.on(constants.SOCKET_GET_CHAT, data => {
this.setState(previousState => ({
recentChat: [...previousState.recentChat, data]
}))
});
You'll want to do this as you'll constantly receive new messages from the sockets, and will want to retain the chats in this.state.recentChat and append new chats.

Related

Function Binding Issues on Callbacks

I'm working with WebSocket and having an issue with a function showing incorrect data, All my code used to work with a Class-based component, I'm only trying to convert a class component that actually calls the connect method of Websocket to a functional-based component. The Websocket is class-based for instance.
So I have a WebSocket which on socket New Message sets callbacks.
socketNewMessage(data) {
console.log(data);
const parsedData = JSON.parse(data);
const command = parsedData.command;
if (command === "userChatGroups") {
this.callback[command](parsedData.chatGRoups);
}
if (command === "new_userChatGroups") {
this.callback[command](parsedData.chatGRoup);
}
}
and here are the callbacks defined -
addCallbacks(userChatGroups, newUserChatGroup) {
this.callback["userChatGroups"] = userChatGroups;
this.callback["new_userChatGroups"] = newUserChat;
}
and then websocket finally return -
const WebSocketInstance = Websocketservice.getInstance();
export default WebSocketInstance;
Now the class-based component which I'm trying to convert to functional based -
let's call this component Chats-
this calls the connect method and a Websocket instance is returned.
useEffect(() => {
if (loggedInUserDetail) {
WebSocketInstance.connect(loggedInUserDetail[0].id);
}
}, [loggedInUserDetail]);
I have a helper function which checks the status of websocket -
function waitForSocketConnection(callback) {
setTimeout(() => {
if (WebSocketInstance.state() === 1) {
console.log("connection is secure");
callback();
return;
} else {
console.log("waiting for connection");
waitForSocketConnection(callback);
}
}, 10);
}
and I check the status and map my callbacks - here is the problem-
useEffect(() => {
waitForSocketConnection(() => {
WebSocketInstance.addCallbacks(
setChatGroups,
addNewChatGroup
);
});
}, [loggedInUserDetail]);
I have a state to manage CHatGroups -
const [groups, setGroups] = useState([]);
the setChatGroups (which initially loads all the groups the users are associated with works fine and sets the state of chatsGroups) as -
const setChatGroups = useCallback((userChatGroups) => {
setGroups(userChatGroups); //whatever data it recieved from the websocket.
}, []);
but the function addNewChatGroup always shows groups value as an empty array [] (it was updated earlier with setChatGroups). If I manually check the value of groups it is an array of n length, but in addNewChatGroup function, it always shows an empty array with the initial value [].
const addNewChatGroup = useCallback(
(newCHatGroup) => {
console.log(groups); **error -> this is always empty array**
// here I have to update. add the received value with the previous
},
[groups] // callbacks used hoping this will bind
);
In the class-based component I used to set callbacks on the constructor and used to bind with this, but I'm not able to do it here, can anyone help what I'm missing?
I'm sure it is a binding issue. maybe. May I know the reason for this binding failure?
Well if I understood correctly your problem, it could be link to multiple things.
The problem actually is I don't have a clear view on all your components, maybe you can try to paste a sandbox link or something like that with a "simple structure".
I tried to reproduced a typescript version, I don't know if it could help:
class MyWebSocket {
private static _instance: MyWebSocket;
public callbacks: any = {};
public connected: boolean = false;
public socketNewMessage(data: any): void {
const parsedData = JSON.parse(data);
console.log('new message received:', parsedData);
const command = parsedData.command;
if (command === "new_userChatGroups") {
this.callbacks[command](parsedData.newGroupAdded);
}
}
public addCallbacks(elements: {command: string, func: Function}[]) {
console.log('adding callbacks...', elements);
elements.forEach(element => {
this.callbacks[element.command] = element.func;
});
}
public connect(): void {
setTimeout(() => this.connected = true, 1100);
}
public static getInstance(): MyWebSocket {
return this._instance || (this._instance = new MyWebSocket());
}
}
class SocketUtils {
static waitForSocketConnection(callback: any): void {
const waitingInterval = setInterval(() => {
if (MyWebSocket.getInstance().connected) {
console.log('socket is connected! processing callback...');
clearInterval(waitingInterval);
callback();
return;
} else {
console.log('socket is not connected after 1sec, waiting...');
}
}, 1000);
}
}
class Chat {
groups: string[] = ['group of JStw'];
new_userChatGroups(group: string) {
this.groups.push(group);
}
}
class Main {
constructor() {
const myChat = new Chat();
MyWebSocket.getInstance().connect();
// waiting connections.
SocketUtils.waitForSocketConnection(() => {
console.log('waitForSocketConnection is triggered, adding callbacks...');
// adding callbacks
MyWebSocket.getInstance().addCallbacks([{command: 'new_userChatGroups', func: myChat.new_userChatGroups.bind(myChat)}]);
});
// Waiting 5min to dispatch an message
setTimeout(() => {
// testing eventing after getting connection
MyWebSocket.getInstance().socketNewMessage(JSON.stringify({command: 'new_userChatGroups', newGroupAdded: 'group of Ranu Vijay'}));
}, 5000);
setTimeout(() => {
console.log('program finished, results of chat groups:', myChat.groups);
}, 10000);
}
}
new Main();
Output:
I'm more specialized on functional component by using react so without a link to investigate all your code, it will be complex to help.
I don't know if you are using a library, but there is this one: https://www.npmjs.com/package/react-use-websocket which seems to be really ultra simple to use without managing socket connection/disconnection.
For me if I had to implement it, I would say:
Component ChatGroup which is using websocket hook const { sendMessage, lastMessage, readyState } = useWebSocket(socketUrl); and contains the state groups, setGroups.
Component Chat which can use the sendMessage from props of ChatGroup component and call it from this component if a join a group.
Then your "parent" component is managing the state and is controlling the data.

How to receive SocketIO events in React while using states

This is part of my code, what I want to do is this component at any time can receive a message on any of the conversations. Sending a message triggers a Socket event which triggers this code below, but I can't seem to get the "latest" conversations, as the useEffect only triggers when the component mounts (at that point my conversations array has zero length).
What I was thinking is that I should include "conversations" on the useEffect's dependency but that would create multiple websocket connection, one each time a Socket.io event is triggered because it does change the state. Is this the best solution? Thanks in advance!
const [conversations, setConversations] = useState<Array<Conversations>>([]);
useEffect(() => {
async function getConversations() {
try {
const { data } = await axios.get("/api/conversations/");
if (data.success) {
setConversations(data.details);
}
} catch (err) {}
}
getConversations();
socketInstance.on("connect", () => {
console.log("Connecting to Sockets...");
socketInstance.emit("authenticate", Cookies.get("token") || "");
});
socketInstance.on("ackAuth", ({ success }) => {
console.log(
success
? "Successfully connected to Sockets"
: "There has been an error connecting to Sockets"
);
});
socketInstance.on("newMessage", (data) => {
const modifiedConversation: Conversations = conversations.find(
(conv: Conversations) => {
return conv.conversationId === data.conversationId;
}
);
modifiedConversation.messages.push({
from: {
firstName: data.firstName,
lastName: data.lastName,
profilePhoto: data.profilePhoto,
userId: data.userId,
},
content: data.content,
timeStamp: data.timeStamp,
});
const updatedConversations = [
...conversations.filter(
(conv) => conv.conversationId !== data.conversationId
),
modifiedConversation,
];
setConversations(updatedConversations);
});
}, []);
While attaching and removing the socket listeners every time conversations changes is a possibility, a better option would be to use the callback form of the setters. The only time you reference the state, you proceed to update the state, luckily. You can change
socketInstance.on("newMessage", (data) => {
const modifiedConversation: Conversations = conversations.find(
// lots of code
setConversations(updatedConversations);
to
socketInstance.on("newMessage", (data) => {
setConversations(conversations => {
const modifiedConversation: Conversations = conversations.find(
// lots of code
setConversations(updatedConversations);
You should also not mutate the state, since this is React. Instead of
modifiedConversation.messages.push({
do
const modifiedConversationWithNewMessage = {
...modifiedConversation,
messages: [
...modifiedConversation.messages,
{
from: {
// rest of the object to add

How to pass data from parent to child in reactJS after the components have rendered

I've done a lot of searching and can't seem to find the answer to this - maybe I'm just not using the right terminology.
What I need to do is pass data from a WebSocket component, down to a child component. I'm already passing the WebSocket via props to the child so that it can use the send() function and send data to the socket. I need to also pass any received data via onmessage. Setting this up in the usual way inside the child doesn't work.
What I need to happen is when the data is received in the socket it gets sent to the child, with a function inside the child to then do something with it (send it via MIDI using the Web MIDI API)
Parent
class Socket extends Component {
constructor(props) {
super(props);
this.state = {
ws: null,
};
}
componentDidMount() {
this.connect();
}
connect = () => {
var ws = new WebSocket("ws://127.0.0.1:8000/ws/");
ws.onopen = () => {
this.setState({ ws: ws });
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
var midi = data["command"]; // Need to send this to the child somehow.
};
......
}
render() {
return <MIDIComponent websocket={this.state.ws} />;
}
}
EDIT: So I've managed to get the data from the parent to the child, and I've rendered it to the screen for testing. But I can't extract it inside the functions I need. I've tried combinations of using arrow functions, binding 'this' etc. I either can't access this or the midi ports either come back as undefined or null, the default value.
Child
class MIDIComponent extends Component {
constructor(props) {
super(props);
this.state = {
midiInput: null,
midiOutput: null,
};
}
componentDidMount() {
const that = this;
this.setupMIDI(that);
}
setupMIDI = (that) => {
navigator.requestMIDIAccess({ sysex: true }).then(onMIDISuccess);
function onMIDISuccess(midiAccess) {
that.setState({ midiInput: Array.from(midiAccess.inputs.values())[0] });
that.setState({ midiOutput: Array.from(midiAccess.outputs.values())[1] });
that.state.midiInput.onmidimessage = getMIDIMessage;
// storing the midi ports in the state like this, but it doesnt work.
}
function getMIDIMessage(msg) {
console.log(msg.data);
that.props.websocket.send(
JSON.stringify({ message: Array.from(msg.data), type: "config" })
);
}
};
sendMIDIMessage = (msg) => {
this.state.midiOutput.send(msg); // need to get to the midiOutput port here to send the data
};
render() {
return <div key={this.props.midi}>{this.props.midi}</div>; // Just using this to render the data to the screen for testing
}
}
I should probably mention that I will be eventually having two Child Components that will need to receive data from the Socket depending on the type of data received. At the moment I'd just like to get it set up using one. Any help would be greatly appreciated.
Simply save the received data in the state as well like this:
class Socket extends Component {
constructor(props) {
super(props);
this.state = {
ws: null,
midi: [] // Create an empty array so that the child always received something and not undefined
};
}
componentDidMount() {
this.connect();
}
connect = () => {
var ws = new WebSocket("ws://127.0.0.1:8000/ws/");
ws.onopen = () => {
this.setState({ ws: ws });
};
ws.onmessage = (e) => {
const data = JSON.parse(e.data);
const midi = data["command"]; // Need to send this to the child somehow.
this.setState({
midi // Save the received data in the state
});
};
}
render() {
const {ws, midi} = this.state; // Extract the data from the state
return <MIDIComponent websocket={ws} midi={midi}/>; // Pass the data as a prop to the child
}
}

How to render data in react getting undefined state?

I am fetching data inside componentDidMount but i get undefined during initial render and again render happens and during that time the state variable gets populated. Now when it is not undefined and after population I want to destructure it and display the data inside my component.
Note: getProjectDetails() makes a GET req to populate the data.
I am getting typer error name of undefined.
constructor(props) {
super(props);
this.state = {
projectDetails: ''
};
}
componentDidMount() {
getProjectDetails(this.props.logged_in_user, this.props.projectId)
.then( res => {
this.setState({projectDetails: res});
})
.catch(err => {
console.log('Error: ' + err);
})
}
//Inside render()
render() {
console.log('Project details from API endpoint: ', this.state.projectDetails.project)
const { projectDetails } = this.state;
console.log('Destructure: ', projectDetails);
const project = this.state.projectDetails.project;
let {
name,
location,
city,
builder_name } = project;
You could check with the following if the state is set:
render() {
if(this.state.projectDetails === ''){
return <div>Loading</div>;
}
else{
return <div>{this.state.projectDetails.project}</div>
}
}
So as long as the state is false, Loading will be returned, if there is a value, then this.state.projectDetails.project gets returned. I hope that helps.
Edit:
The render method will be called twice, before the data is fetched and then, after the state is set. So this would only return the data, if its really set.

Why my onmessage method can't receive event data?

I'm using websocket to make an easy web app.
My server program has a very easy function that when it receives a message from a client, it'll return the same message to the client.
My code is below:
class Seat extends Component {
constructor(props){
super(props);
this.state = {
'seatLabel' : 'Empty'
}
this.Message = this.Message.bind(this);
this.ws = new WebSocket('ws://localhost:8080/');
this.ws.onmessage = this.Message;
this.ws.onopen = function(){
this.send("sadf");
}
}
Message(e){
alert(e.data);
}
handleClick(p) {
this.ws.send(`${p.pname}`)
if (this.props.sitdown(p)) {
p.sit = !p.sit;
this.setState({
'seatLabel': p.sit ? p.pname : "Empty"
})
}
}
render(){
return (<div onClick={this.handleClick.bind(this, this.props.user)}>{this.state.seatLabel}</div>)
}
}
'onmessage' event is triggered when the object 'ws' is 'onopen' status, I can receive the message from the server.
But when I clicked the div to send a message, I can't receive the response message.
So I want to know what happened, why I can't receive the message.

Categories