How to Combine React Native with Socket.Io

Is it possible to combine React Native with socket.io

For those like me stumbling across this question looking how to integrate socket.io with react native.

Since React Native has supported websockets for a short time now, you can now set up web sockets really easily with Socket.io. All you have to do is the following

  1. npm install socket.io-client
  2. first import react-native
  3. assign window.navigator.userAgent = 'react-native';
  4. import socket.io-client/socket.io
  5. in your constructor assign this.socket = io('localhost:3001', {jsonp: false});

So in all it should look like this after npm installing socket.io-client:

import React from 'react-native';

// ... [other imports]

import './UserAgent';

import io from 'socket.io-client/socket.io';

export default class App extends Component {
constructor(props) {
super(props);
this.socket = io('localhost:3001', {jsonp: false});
}

// now you can use sockets with this.socket.io(...)
// or any other functionality within socket.io!

...
}

and then in 'UserAgent.js':

window.navigator.userAgent = 'react-native';

Note: because ES6 module imports are hoisted, we can't make the userAgent assignment in the same file as the react-native and socket.io imports, hence the separate module.

EDIT:

The above solution should work, but in the case it doesn't try create a separate socketConfig.js file. In there import anything that is needed, including const io = require('socket.io-client/socket.io'); and having window.navigator.userAgent = 'react-native'; BEFORE requiring socket.io-client. Then you can connect your socket there and have all listeners in one place. Then actions or functions can be imported into the config file and execute when a listener receives data.

React Native, NodeJS, Socket.io

after many attempts i managed to get it working thanks to Srikanth's answer on this Creating a private chat between a key using a node.js and socket.io.

CLIENT

useEffect(() => {
const newsocket =io.connect("http://192.168.1.103:9000")
setMessages(message.Messages)

newsocket.on('connect', msg => {
console.log(`user: ${user.id} has joined conversation ${message.id}`)
setSocket(newsocket)
newsocket.emit('subscribe', message.id);
});

newsocket.on("send_message", (msg) => {
console.log("this is the chat messages:", msg);
setMessages(messages => messages.concat(msg))
});

return(()=>newsocket.close());

}, []);

const onSend = (ConversationId,senderId,receiverId,message) => {
console.log("sent")
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 });
setText("")
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
};

SERVER

io.on('connection',(socket)=>{
console.log('User '+socket.id+' connected')

socket.on('subscribe', (room)=> {
console.log('joining room', room);
socket.join(room);
});

socket.on('message', (data) => {
console.log(data)
console.log('sending room post',data.ConversationId)
io.sockets.in(data.ConversationId).emit('send_message', { message:
data.message, receiverId:
data.to,senderId:data.from,conversationId:data.ConversationId });
})
})

I have read many comments that suggested not to use rooms in 1-1 private messaging but this was the only possible way of getting it to work.

Combining socket.io with a redux reducer

You can emit the new message from the node server like.

let msg = {message: 'Actual Message', sender: 'Name of sender'}
io.on('connection', socket => {
socket.on('chat message', msg => {
io.emit('chat message', msg);
});
});

Then in you chat component listen the chat message event.

 componentDidMount() {
this.socket = io("http://server.address:port");
this.socket.on("chat message", msg => {
this.props.onMessageReceivedAction (msg); // import action and will execute on message received event
});
});

Then add the action :

export const onMessageReceivedAction = (params) => {
return {type: MESSAGE_RECEIVED, payload:msg};
};

Add message received reducer case to your reducer

case MESSAGE_RECEIVED:

let newMessage = {
sender: payload.sender,
message: payload.message
};

return { ...state, chats: state.chats.push(newMessage) };

This will return the new state and the component props mapped to the above state will get re-rendered.

Hope this will answer your question.

socket.io client receiving multiple calls to same event in react native

I think you can try adding socket event handler only when your Chat component mounted.

In functional component, you can use React.useEffect().

refer to below

React.useEffect(() => {
socket.on("newMessage", ({ msgBody, sender }) => {
setData((oldMessages) => [...oldMessages, msgBody]);
});
},[]);

Socket.io opening multiple connections with React-Native

So here I come with an answer. I'll try to leave an answer as the one I'd like to find. A sort of tutorial about how to include Socket.io in React-native. Please, if you know a better solution, write it down.
As I wrote, the problem is that socket in React-Native is global and in this way I wrong implementation is just more evident.
First of all, I initialized socket in the wrong place. The correct place I found is in App.js, where the router is. I remove some code for clarity.

// important to access the context of React
import PropTypes from 'prop-types';
// Library used to save encrypted token
import * as Keychain from 'react-native-keychain';
// url to address
import { BASIC_WS_URL } from '../../api';// library to encrypt and decrypt data
const io = require('socket.io-client/dist/socket.io');

prepare this function within contructor and componentDidMount:

  state = {}
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}

keichain is a promise, so it won't work in componentDidMount. To make it work, you have to do the following, so every step will wait for the previous to be done:

async componentWillMount() {
const response = await Keychain.getGenericPassword();
const websocket = await io(BASIC_WS_URL, {
jsonp: false,
// forceNew:true,
transports: ['websocket'],
query: {
token: response.password
}
});
await websocket.connect();
await websocket.on('connect', (socket) => {
console.log('Sono -> connesso!');
});
await websocket.on('reconnect', (socket) => {
console.log('Sono riconnesso!');
});
await websocket.on('disconnect', (socket) => {
console.log('Sono disconnesso!');
});
await websocket.on('error', (error) => {
console.log(error);
});
// a function imported containing all the events (passing store retrieved from context declare at the bottom)
await socketEvents(websocket, this.context.store);

// then save the socket in the state, because at this point the component will be already rendered and this.socket would be not effective
await this.setStateAsync({websocket: websocket});
}

Remember to remove the console.logs then. They are there just for verification. Right after this, remeber to disconnect when unmounting:

  componentWillUnmount() {
this.state.websocket.disconnect()
}

And right after this, save the socket in the context:

  getChildContext() {
return {websocket: this.state.websocket};
}

Remeber to declare the context at the bottom of the component:

App.childContextTypes = {
websocket: PropTypes.object
}
// access context.type to get the store to pass to socket.io initialization
App.contextTypes = {
store: PropTypes.object
}

So, final result is this:

  ...
// important to access the context of React
import PropTypes from 'prop-types';
// Library used to save encrypted token
import * as Keychain from 'react-native-keychain';
// url to address
import { BASIC_WS_URL } from '../../api';// library to encrypt and decrypt data
const io = require('socket.io-client/dist/socket.io');
...


class App extends Component {
constructor() {
super();
...
}
state = {}
setStateAsync(state) {
return new Promise((resolve) => {
this.setState(state, resolve)
});
}
// set the function as asynchronous
async componentWillMount() {
//retrieve the token to authorize the calls
const response = await Keychain.getGenericPassword();
// initialize the socket connection with the passwordToken (wait for it)
const websocket = await io(BASIC_WS_URL, {
jsonp: false,
// forceNew:true,
transports: ['websocket'], // you need to explicitly tell it to use websockets
query: {
token: response.password
}
});
// connect to socket (ask for waiting for the previous initialization)
await websocket.connect();
await websocket.on('connect', (socket) => {
console.log('Sono -> connesso!');
});
await websocket.on('reconnect', (socket) => {
console.log('Sono riconnesso!');
});
await websocket.on('disconnect', (socket) => {
console.log('Sono disconnesso!');
});
await websocket.on('error', (error) => {
console.log(error);
});
// a function imported containing all the events
await socketEvents(websocket, this.context.store);
await this.setStateAsync({websocket: websocket});
}
componentWillUnmount() {
this.state.websocket.disconnect()
}
getChildContext() {
return {websocket: this.state.websocket};
}
render() {
return (
... // here goes the router
);
}
}
App.childContextTypes = {
websocket: PropTypes.object
}
// access context.type to get the store to pass to socket.io initialization
App.contextTypes = {
store: PropTypes.object
}
export default App;

then, in any page/container, you can do like this. -> declare the context in the bottom of the component:

Main.contextTypes = {
websocket: PropTypes.object
}

And when you dispatch an action, you will be able then to emit:

this.props.dispatch(loadNotif(this.context.websocket));

In the action creator, you will emit like this:

exports.loadNotif = (websocket) => {
return function (dispatch) {
// send request to server
websocket.emit('action', {par: 'blablabla'});
};
};

I hope it will help somebody.

React Native Socket.io How to Connect to Local Node Server from Device

Solved by tunneling localhost:3000 via ngrok and allowing an exception domain.

In your info.plist you need the following under App Transport Security Settings

Sample Image

On a mac command line run

  • brew cask install ngrok
  • ngrok http 3000

Then grab the outputted ngrok.io URL and use it in your io.connect() call and you should be set.



Related Topics



Leave a reply



Submit