I am new to using socket.io with react native.I am able to connect the socket.io instance with my node.js backend but the socket.io client is emmitting events multiple times.The app has grown quite complex so,I am also trying to explain what I did.
Main.js (User is redirected here from App.js)
I am using a library called react-native-tab-view and I used useContext to pass the socket instance.I have checked that the hook is working properly.
export const SocketObj = createContext()
const Main = ({ route }) => {
let [socket, setSocket] = useState(undefined)
const routesLength = useNavigationState(state => state.routes.length);
const connect = async () => {
if (routesLength == 1) {
setSocket(io("http://192.168.43.115:8000", {
transports: ['websocket'],
query: {
token: await AsyncStorage.getItem("token")
}
}))
} else {
const { socketInstanse } = route.params
setSocket(socketInstanse)
}
}
connect()
const layout = useWindowDimensions();
const [index, setIndex] = useState(0);
const [routes] = useState([
{ key: 'first', title: 'CHAT' },
{ key: 'second', title: 'PEOPLE' },
]);
const renderScene = SceneMap({
first: SplashScreen,
second: PeopleScreen,
});
return (
<SocketObj.Provider value={socket} >
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={(number) => {
setIndex(number)
}}
initialLayout={{ width: layout.width }}
/>
</SocketObj.Provider>
);
}
export default Main;
PeopleScreen.js
The user is directed here from Main.js. I have used the context api here to get the socket instance.
As you can see , i am using an event to log "Connected" to the console when connected but it is emitting multiple times.I don't want to emit it multiple times.Help me
import { SocketObj } from "./Main"
const PeopleScreen = ({ route, navigation }) => {
let socket = useContext(SocketObj)
socket.on("connect", () => console.log("Connect"))
const [peopleList, setPeople] = useState([])
useEffect(
() => {
const fetchData = async () => {
try {
const response = await API.get('get/users', {
headers: {
'Content-Type': 'application/json',
"auth-token": await AsyncStorage.getItem("token")
}
})
setPeople(response.data)
} catch (err) {
console.log(err.response)
}
}
fetchData()
}, []
)
return (
<View style={{
flex: 1,
backgroundColor: '#fff',
width: '100%',
height: '100%'
}} >
<View>
{
peopleList.map(
(i, key) => {
return (
<View style={{
display: 'flex',
flexDirection: 'row',
top: 40,
marginVertical: 5,
marginBottom: 10,
backgroundColor: 'grey',
height: 50
}}
key={key} >
<Text style={{
maxWidth: "50%"
}} >{i.firstName + ' ' + i.email}</Text>
<TouchableOpacity
onPress={
async () => {
console.log(socket)
API.post('post/add-friend', {
friend_email: i.email
}, {
headers: {
"auth-token": await AsyncStorage.getItem("token")
}
}).then(
data => console.log(data.data)
)
}
}
style={{
backgroundColor: 'rgb(255, 105, 105)',
width: 130,
justifyContent: 'center',
alignItems: 'center',
left: 150
}}
activeOpacity={0.6} >
<Text style={{
color: 'white',
}} >Add Friend</Text>
</TouchableOpacity>
</View>
)
}
)
}
</View>
</View >
)
}
export default PeopleScreen
I have ignored unnecessary imports.
Try to put your connect() inside an useEffect with an empty array as second argument of useEffect, then your connect function will be called only at the first render of Main.js.
...
const connect = async () => {
if (routesLength == 1) {
setSocket(io("http://192.168.43.115:8000", {
transports: ['websocket'],
query: {
token: await AsyncStorage.getItem("token")
}
}))
} else {
const { socketInstanse } = route.params
setSocket(socketInstanse)
}
}
useEffect(() => {
connect();
}, []);
...
if (!socket) return null; // or you can return a loading page
return (
<SocketObj.Provider value={socket} >
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={(number) => {
setIndex(number)
}}
initialLayout={{ width: layout.width }}
/>
</SocketObj.Provider>
);
It happens due to initialisation of socket multiple time.Put instance in Main.js .
If socket instance is empty then initialise socket.
{ useEffect(() => { if(!isConnected){ connect() } });
Related
inside userlist.js the code looks like
const renderItems = ({item}) => {
if (item !== undefined) {
const user = {
image:'https://www.w3schools.com/w3images/avatar6.png',
name: item.name,
lastMessage: item.lastChat,
time: item.latest_timestamp,
seen: item.seen,
id: item._id,
};
if(item._id!==userID)return <DocList user={user} nav={nav} sendToParents={fn} from="userlist"/>;
else return null;
}
return null;
};
const DATA = [
{
data: friends,
},
];
return (
<View style={{marginTop:53,marginBottom:130}}>
{friends.length==0?
<View style={{alignSelf:'center',marginTop:90}}>
<Image style={{resizeMode:'stretch',width:350,height:220,alignSelf:'center'}} source={require('../../asserts/No_chats.png')} />
<Text style={{textAlign:'center',fontWeight:'bold',marginTop:10,color:'#838383'}}>No recent chats !</Text>
<Text style={{textAlign:'center',textAlign:'center',width:300,alignSelf:'center',color:'gray'}}>Not yet started conversation? press the green + icon to see the list of users you can chat with.</Text>
</View>
:
<SectionList
sections={DATA}
keyExtractor={(item, index) => index.toString()}
renderItem={renderItems}
renderSectionHeader={({section: {title}}) => (
<View>{title}</View>
)}>
</SectionList> }
</View>
);
};
inside newuser.js it looks like
const handleRender = ({ item }) => {
const user = {
image: 'https://www.w3schools.com/w3images/avatar6.png',
name: item.name,
lastMessage: item.lastChat,
time: item.latest_timestamp,
seen: item.seen,
id: item._id,
};
return (
<DocList user={user} nav={nav} sendToParents={handleCallback} from="newuser" />
);
};
return (
<View>
<FlatList
data={docs}
keyExtractor={(item, index) => index.toString()}
renderItem={handleRender}
/>
</View>
);
};
The DocList component is:
import {socketSubsribe, socketIdentity, socketUnsubscribe} from './SocketServer';
import axios from 'axios';
import {StyleSheet, Text, View, Image, TouchableOpacity} from 'react-native';
import AsyncStorage from '#react-native-async-storage/async-storage';
import {BASE_URL, BASE_URL1} from '../../Components/Urls';
import { nowTime } from '../../Components/Clock';
import socket from "../components/SocketServer";
const DocList = ({user, nav, sendToParents,from}) => {
const [latestmsg,setLatestmsg]=useState(user.lastMessage);
const [userImg,setUserImg]=useState(null);
const [seen,setSeen]=useState(user.seen);
const [roomID,setRoomID]=useState();
let latmsg;
// const textBold = user.seen ? 'normal' : 'bold';
const textBold ='bold';
const subscribe=async()=>{
//await AsyncStorage.removeItem("chatUsers");
const resultRoomidChat= await AsyncStorage.getItem(`${user.id}`);
if(resultRoomidChat){
const {roomId,chat}=JSON.parse(resultRoomidChat);
console.log(roomId,"ROOMId");
setRoomID(roomId);
}
const { userId,userToken} = JSON.parse(
await AsyncStorage.getItem('userToken')
);
const GET_IMAGE_URL=BASE_URL1+"/api/user/getUserImg";
const resultUser= await axios.get(GET_IMAGE_URL+`/${user.id}`,{
headers:{
authorization:`Bearer ${userToken}`
}
});
//console.log("RESULTUSER",resultUser.data.picture,typeof(resultUser.data.picture));
if(resultUser.data.picture){
// console.log("USER Image",resultUser.data.picture);
let imgsrc=resultUser.data.picture;
setUserImg(imgsrc);
//console.log("IMGSRC",imgsrc);
}
socketIdentity(userId);
//console.log("SUNSCRIBE IDENTITY");
const object =await AsyncStorage.getItem(`${user.id}`);
if(object){
//console.log("SUNSCRIBE OTHERUSER");
const {roomId}=JSON.parse(object);
socketSubsribe(roomId, user.id);
}
}
const handleRequest = async () => {
try {
const {userToken, userId} = JSON.parse(
await AsyncStorage.getItem('userToken'),
);
const URL= BASE_URL+'/api/chat/initiate';
const data={
userIds: [userId, user.id],
type: 'customer-to-doctor',
}
console.log("1",URL, data);
const res= await axios.post(URL,data,{
headers:{
authorization:`Bearer ${userToken}`
}
})
console.log("2",res.data,res.status);
if (res.data && res.status ) {
const {chatRoom} = res.data;
const id = chatRoom.chatRoomId;
console.log("CHATROOM ID iS",id)
const object1=await AsyncStorage.getItem(`${user.id}`);
console.log("object1 ",object1);
if(object1){
const object=JSON.parse(object1);
const modifiedObject={
roomId:id,
medium:'app',
chat:[...object.chat]
}
await AsyncStorage.setItem(`${user.id}`, JSON.stringify(modifiedObject));
console.log("USER.ID",user.id,modifiedObject);
}else{
await AsyncStorage.setItem(`${user.id}`, JSON.stringify({roomId: id,medium:'app', chat:[]}));console.log("after setting ROOM ID");
}
}
} catch (e) {
console.log(e, '==>> error in app doclist');
}
};
const call2doclist=async(msg,postedByUser,seen)=>{
try{
const {userInfo} = JSON.parse(
await AsyncStorage.getItem('chatUsers'),
);
const {userId} = JSON.parse(
await AsyncStorage.getItem('userToken'),
);
//console.log("uI in DOCLIST");
if(userInfo){
console.log("hi call2doclist");
let i=0;
for(i=0;i<userInfo.length;i++){
if(userInfo[i]._id==user.id){
//console.log("hi3");
console.log("POSTEDBYVUSER CHECK",postedByUser,userId,user.id);
if(seen==true && postedByUser==userId){
console.log('hi4');
userInfo[i].seen=true;
setSeen(true);
}else{
userInfo[i].seen=false;
setSeen(false);
}
userInfo[i].lastChat=msg;
setLatestmsg(msg);
console.log("userInfo[i]",userInfo[i],seen);
break;
}
}
await AsyncStorage.setItem(
'chatUsers',
JSON.stringify({userInfo:[...userInfo]}),
);
}
user.lastMessage=msg;
setLatestmsg(user.lastMessage);
console.log("SEEN",seen);
}catch(e){
console.log("Error in call2 inside doclist",e)
}
}
const handlePress = async () => {
setLatestmsg('');
console.log(latestmsg,from);
await handleRequest();
if(from=="newuser"){
sendToParents(user.id);
}
console.log("inside doclist",user);
console.log("hey");
nav.navigate('ChatSc', {name: user.name, id: user.id ,from:'app',call2doclist:{call2doclist}});
};
const updateChat=async(msg)=>{
try{
const data = JSON.parse(await AsyncStorage.getItem(`${user.id}`));
await AsyncStorage.removeItem(
`${user.id}`
);
data.chat.unshift({
side:msg.side,
text:msg.text,
message_id:msg.message_id,
time: msg.time,
});
//console.log("i saev");
await AsyncStorage.setItem(
`${user.id}`,
JSON.stringify({...data}),
);
}catch(e){
console.log("Error in updating chats",e);
}
}
const update2=async(msg)=>{
await updateChat(msg)
}
socket.off('new message'+`${roomID}`).on('new message'+`${roomID}`,(data,err)=>{
try{
if(err){ console.log("error in socket recieving",err)}
if(!err){
console.log("inside DOCLIST for app");
const msg={
// postedByUser:data.message.postedByUser._id,
message_id:data.message.postId,
side:user.id === data.message.postedByUser._id ? 'left':'right',
text:data.message.message.messageText,
time:data.message.message.time,
timestamp:nowTime()
};
update2(msg);
call2doclist(data.message.message.messageText,data.message.postedByUser._id,false);
}
}catch(e){
console.log(e,"Error in chatScreen of inside doclist ",e.message)
}
});
const subscribe2=async()=>{
await subscribe();
console.log("hi");
}
useEffect(()=>{
subscribe2();
},[]);
return (
<TouchableOpacity onPress={()=>handlePress()}>
<View style={styles.topContainer}>
{/* user Image */}
<View style={{...styles.ImageContainer}}>
<Image source={{uri:userImg?userImg:user.image}} style={{...styles.imageStyle}} />
</View>
{/* user data value */}
<View style={styles.userContainer}>
<View style={styles.userInfo}>
<View>
<Text>{user.name}</Text>
<Text style={{fontWeight: textBold,paddingRight:30}}>{seen==false?latestmsg:''}</Text>
</View>
<View>
</View>
</View>
</View>
</View>
</TouchableOpacity>
);
};
export default DocList;
const styles = StyleSheet.create({
imageStyle: {
width: 70,
height: 70,
borderRadius: 70,
},
topContainer: {
paddingLeft:10,
flexDirection: 'row',
width: '100%',
height: 78,
marginTop: 10,
marginBottom: 10,
paddingTop: 0,
},
ImageContainer: {
width: 70,
alignItems: 'flex-end',
},
userContainer: {
flex: 1,
marginLeft: 5,
justifyContent: 'space-around',
},
userInfo: {
flexDirection: 'row',
justifyContent: 'space-between',
padding: 5,
},
});
through userlist.js the console.log prints 2, response data and status , but through newuser it doesn't print these things although it prints 1, URL, data. It means there's some problem there itself but in the former case, it does hit the API and logs in the backend too, but in the latter case it doesn't hit and doesn't log anything there in the backend.
Check if the item parameter is correctly passed to the handleRender function in newuser.js. Also check in the Networks Tab in console whether is it actually hitting the API or because of authentication issue the call must be getting rejected.
Am new to webRTC and am trying to create a react native app with video calling functionality using this tutorial here as an example to follow https://dipanshkhandelwal.medium.com/video-calling-using-firebase-and-webrtc-14cc2d4afceb
However i keep getting this error on iOS and on android the app just closes once i try to join a call. The error i get on iOS says:
JSON value '{
}' of type NSMutableDictionary cannot be converted to .sdp must not be null
+[RCTConvert(WebRTC) RTCSessionDescription:]
RCTConvert+WebRTC.m:22
__41-[RCTModuleMethod processMethodSignature]_block_invoke_16
-[RCTModuleMethod invokeWithBridge:module:arguments:]
facebook::react::invokeInner(RCTBridge*, RCTModuleData*, unsigned int, folly::dynamic const&, int, (anonymous namespace)::SchedulingContext)
facebook::react::RCTNativeModule::invoke(unsigned int, folly::dynamic&&, int)::$_0::operator()() const
invocation function for block in facebook::react::RCTNativeModule::invoke(unsigned int, folly::dynamic&&, int)
_dispatch_call_block_and_release
_dispatch_client_callout
_dispatch_lane_serial_drain
_dispatch_lane_invoke
_dispatch_workloop_worker_thread
_pthread_wqthread
start_wqthread
in metro this comes up as an error:
Possible Unhandled Promise Rejection (id: 0):
Object {
"message": "SessionDescription is NULL.",
"name": "SetRemoteDescriptionFailed",
}
A sample of the offer created in firstore in the respective room is below. Am suspecting the sdp might be invalid or something.
_sdp:
v=0o=- 765269967391877801 2 IN IP4 127.0.0.1s=-t=0 0a=group:BUNDLE audio videoa=msid-semantic: WMS a3a6a14c-980f-4052-a881-e53290c2e9a8m=video 100c=IN IP4 0.0.0.0a=rtcp:9 IN IP4 0.0.0.0a=ice-ufrag:qfEea=ice-pwd:aMtS7ouM3ykOCV6z7jx0hhlSa=ice-options:trickle renominationa=fingerprint:sha-256 01:13:79:2E:BA:E3:67:9A:77:66:90:00:D7:62:BA:31:2C:48:FC:EB:8C:21:22:7E:5B:9D:0E:71:82:5F:63:5Aa=setup:actpassa=mid:audioa=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-levela=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-timea=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01a=sendrecva=rtcp-muxa=rtpmap:111 opus/48000/2a=rtcp-fb:111 transport-cca=fmtp:111 minptime=10;useinbandfec=1a=rtpmap:103 ISAC/16000a=rtpmap:9 G722/8000a=rtpmap:102 ILBC/8000a=rtpmap:0 PCMU/8000a=rtpmap:8 PCMA/8000a=rtpmap:105 CN/16000a=rtpmap:13 CN/8000a=rtpmap:110 telephone-event/48000a=rtpmap:113 telephone-event/16000a=rtpmap:126 telephone-event/8000a=ssrc:866776128 cname:OXEDL0qxAsrhPmB7a=ssrc:866776128 msid:a3a6a14c-980f-4052-a881-e53290c2e9a8 256a9ac0-8163-45b5-ae52-0ece88651c51a=ssrc:866776128 mslabel:a3a6a14c-980f-4052-a881-e53290c2e9a8a=ssrc:866776128 label:256a9ac0-8163-45b5-ae52-0ece88651c51m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127c=IN IP4 0.0.0.0a=rtcp:9 IN IP4 0.0.0.0a=ice-ufrag:qfEea=ice-pwd:aMtS7ouM3ykOCV6z7jx0hhlSa=ice-options:trickle renominationa=fingerprint:sha-256 01:13:79:2E:BA:E3:67:9A:77:66:90:00:D7:62:BA:31:2C:48:FC:EB:8C:21:22:7E:5B:9D:0E:71:82:5F:63:5Aa=setup:actpassa=mid:videoa=extmap:14 urn:ietf:params:rtp-hdrext:toffseta=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-timea=extmap:13 urn:3gpp:video-orientationa=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delaya=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-typea=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timinga=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-spacea=sendrecva=rtcp-muxa=rtcp-rsizea=rtpmap:96 VP8/90000a=rtcp-fb:96 goog-remba=rtcp-fb:96 transport-cca=rtcp-fb:96 ccm fira=rtcp-fb:96 nacka=rtcp-fb:96 nack plia=rtpmap:97 rtx/90000a=fmtp:97 apt=96a=rtpmap:98 VP9/90000a=rtcp-fb:98 goog-remba=rtcp-fb:98 transport-cca=rtcp-fb:98 ccm fira=rtcp-fb:98 nacka=rtcp-fb:98 nack plia=rtpmap:99 rtx/90000a=fmtp:99 apt=98a=rtpmap:100 red/90000a=rtpmap:101 rtx/90000a=fmtp:101 apt=100a=rtpmap:127 ulpfec/90000a=ssrc-group:FID 2850853975 3259115858a=ssrc:2850853975 cname:OXEDL0qxAsrhPmB7a=ssrc:2850853975 msid:a3a6a14c-980f-4052-a881-e53290c2e9a8 3f3952f6-cd3d-4d3e-a2dc-81eda77cf516a=ssrc:2850853975 mslabel:a3a6a14c-980f-4052-a881-e53290c2e9a8a=ssrc:2850853975 label:3f3952f6-cd3d-4d3e-a2dc-81eda77cf516a=ssrc:3259115858 cname:OXEDL0qxAsrhPmB7a=ssrc:3259115858 msid:a3a6a14c-980f-4052-a881-e53290c2e9a8 3f3952f6-cd3d-4d3e-a2dc-81eda77cf516a=ssrc:3259115858 mslabel:a3a6a14c-980f-4052-a881-e53290c2e9a8a=ssrc:3259115858 label:3f3952f6-cd3d-4d3e-a2dc-81eda77cf516
Tried everything but can't seem to figure it out. The callee collection also never gets created in the respective rooms in firestore database. Any help with this would be appreciated.
I guess you are trying to use firebase as a signalling medium and want to use react-native-webrtc for the video calling.
Here is the sample code I have for the same solution with the latest libraries and react-native version.
Firebase Installation React Native.
Just set up ios and android using this above link and then use the below code for reference.
import React, {useEffect, useState} from 'react';
import {Modal, TextInput, ToastAndroid, View} from 'react-native';
import {ColoredButton} from '../app/common/commonViews';
import {
mediaDevices,
MediaStream,
RTCIceCandidate,
RTCPeerConnection,
RTCSessionDescription,
RTCView,
} from 'react-native-webrtc';
import firestore from '#react-native-firebase/firestore';
const Home = () => {
const [localStream, setLocalStream] = useState(null);
const [remoteStream, setRemoteStream] = useState(new MediaStream());
const [modalVisible, setModalVisible] = useState(false);
const [roomName, setRoomName] = useState('2277');
let peerConnection;
const configuration = {
iceServers: [
{urls: 'stun:stun.services.mozilla.com'},
{urls: 'stun:stun.l.google.com:19302'},
],
};
let isFront = true;
useEffect(() => {
mediaDevices.enumerateDevices().then((sourceInfos) => {
console.log(sourceInfos);
let videoSourceId;
for (let i = 0; i < sourceInfos.length; i++) {
const sourceInfo = sourceInfos[i];
if (
sourceInfo.kind == 'videoinput' &&
sourceInfo.facing == (isFront ? 'front' : 'environment')
) {
videoSourceId = sourceInfo.deviceId;
}
}
mediaDevices
.getUserMedia({
audio: true,
video: {
width: 640,
height: 480,
frameRate: 30,
facingMode: isFront ? 'user' : 'environment',
deviceId: videoSourceId,
},
})
.then((stream) => {
setLocalStream(stream);
// Got stream!
})
.catch((error) => {
// Log error
});
});
}, [isFront]);
async function createRoom() {
const roomRef = firestore().collection('rooms').doc(roomName);
console.log(roomRef);
console.log('Create PeerConnection with configuration: ', configuration);
peerConnection = new RTCPeerConnection(configuration);
peerConnection.addStream(localStream);
// Code for collecting ICE candidates below
const callerCandidatesCollection = roomRef.collection('callerCandidates');
// Code for collecting ICE candidates above
peerConnection.onicecandidate = function (event) {
if (!event.candidate) {
console.log('Got final candidate!');
return;
}
console.log('Got candidate: ', event.candidate);
callerCandidatesCollection.add(event.candidate.toJSON());
};
peerConnection.onicegatheringstatechange = () => {
console.log(
`ICE gathering state changed: ${peerConnection.iceGatheringState}`,
);
};
peerConnection.onconnectionstatechange = () => {
console.log(`Connection state change: ${peerConnection.connectionState}`);
};
peerConnection.onsignalingstatechange = () => {
console.log(`Signaling state change: ${peerConnection.signalingState}`);
};
peerConnection.oniceconnectionstatechange = () => {
console.log(
`ICE connection state change: ${peerConnection.iceConnectionState}`,
);
};
console.log('Created offer:', offer);
// Code for creating a room below
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
const roomWithOffer = {
offer: {
type: offer.type,
sdp: offer.sdp,
},
};
await roomRef.set(roomWithOffer);
console.log(`New room created with SDP offer. Room ID: ${roomRef.id}`);
ToastAndroid.show(
`New room created with SDP offer. Room ID: ${roomRef.id}`,
ToastAndroid.SHORT,
);
// Code for creating a room above
peerConnection.onaddstream = (event) => {
setRemoteStream(event.stream);
};
// Listening for remote session description below
roomRef.onSnapshot(async (snapshot) => {
const data = snapshot.data();
if (!peerConnection.currentRemoteDescription && data && data.answer) {
console.log('Got remote description: ', data.answer);
const rtcSessionDescription = new RTCSessionDescription(data.answer);
await peerConnection.setRemoteDescription(rtcSessionDescription);
}
});
// Listening for remote session description above
// Listen for remote ICE candidates below
roomRef.collection('calleeCandidates').onSnapshot((snapshot) => {
snapshot.docChanges().forEach(async (change) => {
if (change.type === 'added') {
let data = change.doc.data();
console.log(`Got new remote ICE candidate: ${JSON.stringify(data)}`);
await peerConnection.addIceCandidate(new RTCIceCandidate(data));
}
});
});
// Listen for remote ICE candidates above
}
async function joinRoomById(roomId) {
const roomRef = firestore().collection('rooms').doc(`${roomId}`);
const roomSnapshot = await roomRef.get({
source: 'server',
});
console.log('Got room:', roomSnapshot.exists);
if (roomSnapshot.exists) {
console.log('Create PeerConnection with configuration: ', configuration);
peerConnection = new RTCPeerConnection(configuration);
peerConnection.addStream(localStream);
// Code for collecting ICE candidates below
const calleeCandidatesCollection = roomRef.collection('calleeCandidates');
peerConnection.onicecandidate = function (event) {
if (!event.candidate) {
console.log('Got final candidate!');
return;
}
console.log('Got candidate: ', event.candidate);
calleeCandidatesCollection.add(event.candidate.toJSON());
};
peerConnection.onicegatheringstatechange = () => {
console.log(
`ICE gathering state changed: ${peerConnection.iceGatheringState}`,
);
};
peerConnection.onconnectionstatechange = () => {
console.log(
`Connection state change: ${peerConnection.connectionState}`,
);
};
peerConnection.onsignalingstatechange = () => {
console.log(`Signaling state change: ${peerConnection.signalingState}`);
};
peerConnection.oniceconnectionstatechange = () => {
console.log(
`ICE connection state change: ${peerConnection.iceConnectionState}`,
);
};
// Code for collecting ICE candidates above
peerConnection.onaddstream = (event) => {
setRemoteStream(event.stream);
};
// Code for creating SDP answer below
const offer = roomSnapshot.data().offer;
console.log('Got offer:', offer);
await peerConnection.setRemoteDescription(
new RTCSessionDescription(offer),
);
const answer = await peerConnection.createAnswer();
console.log('Created answer:', answer);
await peerConnection.setLocalDescription(answer);
const roomWithAnswer = {
answer: {
type: answer.type,
sdp: answer.sdp,
},
};
await roomRef.update(roomWithAnswer);
// Code for creating SDP answer above
// Listening for remote ICE candidates below
roomRef.collection('callerCandidates').onSnapshot((snapshot) => {
snapshot.docChanges().forEach(async (change) => {
if (change.type === 'added') {
let data = change.doc.data();
console.log(
`Got new remote ICE candidate: ${JSON.stringify(data)}`,
);
await peerConnection.addIceCandidate(new RTCIceCandidate(data));
}
});
});
// Listening for remote ICE candidates above
}
}
return (
<View style={{flex: 1}}>
<Modal
visible={modalVisible}
transparent={true}
style={{justifyContent: 'center'}}>
<View
style={{
height: 100,
padding: 20,
width: '80%',
alignSelf: 'center',
justifyContent: 'center',
backgroundColor: 'white',
}}>
<TextInput
value={roomName}
onChangeText={(text) => {
setRoomName(text);
}}
placeholder={'Enter text'}
/>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-evenly',
}}>
<ColoredButton
title="close"
onPress={() => {
setModalVisible(!modalVisible);
}}
/>
<ColoredButton
title="create"
onPress={() => {
setModalVisible(!modalVisible);
}}
/>
<ColoredButton
title="join"
onPress={() => {
setModalVisible(!modalVisible);
}}
/>
</View>
</View>
</Modal>
<RTCView
streamURL={localStream ? localStream.toURL() : null}
style={{flex: 1, margin: 20, backgroundColor: 'black'}}
/>
<RTCView
style={{
flex: 1,
marginLeft: 20,
marginRight: 20,
backgroundColor: 'black',
}}
streamURL={remoteStream ? remoteStream.toURL() : null}
/>
<TextInput value={roomName} onChangeText={setRoomName} />
<View
style={{
flexDirection: 'row',
justifyContent: 'space-evenly',
width: '100%',
padding: 20,
}}>
<ColoredButton
title="Create"
onPress={() => {
createRoom().then((r) => {});
}}
/>
<ColoredButton
title="Join"
onPress={() => {
joinRoomById(roomName).then((r) => {});
}}
/>
</View>
</View>
);
};
export default Home;
I'm wondering how to show a genre to each movie in the list. So I have already other details like title, poster_path or description.
The problem comes when I'm trying to show a genre's becouse they are a numbers and I don't know how to translate them to string like 'Horror'
Here is code for fetch data:
fetch(
`https://api.themoviedb.org/3/search/movie?&api_key=${
this.apiKey
}&query=${searchTerm}`,
)
.then(data => data.json())
.then(data => {
const results = data.results;
const movieRows = [];
const movieGen = [];
results.forEach(movie => {
movie.poster_path =
'https://image.tmdb.org/t/p/w500' + movie.poster_path;
const movies = <MovieRow key={movie.id} movie={movie} />;
movieRows.push(movies);
});
this.setState({rows: movieRows});
});
}
and also diplay it in custom component like movie card:
viewMore = () => {
Alert.alert(
`PRODUCTION : ${this.props.movie.original_language}`,
`DESCRIPTION : ${this.props.movie.overview}\n \n GENRE : ${
this.props.movie.genre_ids
}`,
);
};
render() {
return (
<View
style={{
width: '100%',
alignItems: 'center',
justifyContent: 'center',
}}>
<CardCustom
title={this.props.movie.title}
popularity={this.props.movie.popularity}
vote_count={this.props.movie.vote_count}
poster_path={this.props.movie.poster_path}
onPress={this.viewMore}
/>
</View>
);
}
}
export default MovieRow;
This how this looks like in the application:
and the response from api for genre_ids looks like that
I noticed that I have to use separate API for genre's. Now I want to match them to current movie and I dont know how to do it.
Here is a code
class MovieRow extends Component {
constructor() {
super();
this.apiKey = '1bd87bc8f44f05134b3cff209a473d2e';
this.state = {};
}
viewMore = () => {
Alert.alert(
`PRODUCTION : ${this.props.movie.original_language}`,
`DESCRIPTION : ${this.props.movie.overview}\n \n
GENRE : ${this.props.movie.genre_ids}`, // < ------ NUMBER DISPLAYS. HOW TO MATCH GENRE WITH CURRENT MOVIE?
);
this.fetchGenre();
};
fetchGenre() {
fetch(
`https://api.themoviedb.org/3/genre/movie/list?&api_key=${this.apiKey}`,
)
.then(data => data.json())
.then(data => {
const resultGenres = data.genres;
const genreRow = [];
console.log(resultGenres);
resultGenres.map(genre => {
console.log('name', genre.name, 'id', genre.id);
const genres = <Text>genre: {genre.name}</Text>;
genreRow.push(genres);
});
this.setState({gen: genreRow});
});
}
render() {
return (
<View
style={{
width: '100%',
alignItems: 'center',
justifyContent: 'center',
}}>
<CardCustom
title={this.props.movie.title}
popularity={this.props.movie.popularity}
vote_count={this.props.movie.vote_count}
poster_path={this.props.movie.poster_path}
onPress={this.viewMore}
/>
{this.state.gen}
</View>
);
}
}
also this is how response looks like
Regards
Just get an array with all ids of genders and store it into your state, then when you want to display it you will just use a map. like so :
this.state.gender_ids = [
1: "Action",
2: "Horror",
3: "Other gender"
]
this.props.movie.genre_ids.map(id => <Text key={this.state.gender_ids[id]}>{this.state.gender_ids[id]}</Text>)
Just run the following code into your browser's console, I'm pretty sure from now on you'll get the job done.
Example for pairing :
let gendersFromServer = [
{
id: 28,
name: "Action"
},
{
id: 12,
name: "Adventure"
},
{
id: 16,
name: "Animation"
},
// other genders here
]
let gender_ids = [] // intialize with an empty array
gendersFromServer.map(el => gender_ids[el.id] = el.name) // here you transform the data
// here you can setState({gender_ids})
const movie = {
gender_ids: [
28,
12,
16
]
// rest of data
}
// how to get text gender, notice that gender_ids from console log is the one you use in state, not the one from the movie
movie.gender_ids.map(id => console.log(gender_ids[id]))
EDIT 2:
Hope this will solve your problem finally
import React from 'react'
import { SafeAreaView, ScrollView, View, Text } from 'react-native'
const API_KEY = '1bd87bc8f44f05134b3cff209a473d2e'
export default props => {
const [genres, setGenres] = React.useState([])
const [movies, setMovies] = React.useState([])
React.useEffect(() => {
fetch('https://api.themoviedb.org/3/search/movie?&query=Now+You+See+Me&api_key=' + API_KEY)
.then(res => res.json())
.then(result => {
setMovies(result.results)
})
fetch('https://api.themoviedb.org/3/genre/movie/list?&api_key=' + API_KEY)
.then(genre => genre.json())
.then(result => {
const genres = result.genres.reduce((genres,gen) => {
const { id, name } = gen
genres[id] = name
return genres
},[])
setGenres(genres)
})
},[])
const Movies = () => movies.map(movie => {
return (
<View>
<Text>{movie.title}</Text>
<View>
<Text>Genres :</Text>
{
movie.genre_ids.map(id => {
return <Text>{genres[id]}</Text>
})
}
</View>
</View>
)
})
return (
<SafeAreaView style={{flex: 1}}>
<ScrollView style={{flex: 1}}>
<Text>Movies here</Text>
<Movies />
</ScrollView>
</SafeAreaView>
)
}
I'm trying to update state under a promise. But the component is going over and over again infinitely until it max out the heap size. I don't know what I'm missing here.
This is my code
import {useDropzone} from 'react-dropzone';
import File from './File'
import parser from 'subtitles-parser'
const baseStyle = {
flex: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
padding: '20px',
borderWidth: 2,
borderRadius: 2,
borderColor: '#eeeeee',
borderStyle: 'dashed',
backgroundColor: '#fafafa',
color: '#bdbdbd',
outline: 'none',
transition: 'border .24s ease-in-out'
};
const activeStyle = {
borderColor: '#2196f3'
};
const acceptStyle = {
borderColor: '#00e676'
};
const rejectStyle = {
borderColor: '#ff1744'
};
function Drag(props) {
const {
getRootProps,
getInputProps,
isDragActive,
isDragAccept,
isDragReject,
acceptedFiles
} = useDropzone();
const style = useMemo(() => ({
...baseStyle,
...(isDragActive ? activeStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {})
}), [
isDragActive,
isDragReject
]);
const [data, setData] = useState(null)
if(acceptedFiles.length === 1){
const readUploadedFileAsText = (acceptedFiles) => {
const temporaryFileReader = new FileReader();
return new Promise((resolve, reject) => {
temporaryFileReader.onerror = () => {
temporaryFileReader.abort();
reject(new DOMException("Problem parsing input file."));
};
temporaryFileReader.onload = () => {
resolve(parser.fromSrt(temporaryFileReader.result));
};
temporaryFileReader.readAsText(acceptedFiles);
});
};
let file = acceptedFiles[0]
readUploadedFileAsText(file)
.then(res => {
setData({
data: res
})
})
}
return (
<div className="container">
<div {...getRootProps({style})}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<File file={data} />
{console.log(data)}
</div>
);
}
export default Drag
Let me know if I have to use any lifecycle methods. I tried using componentDidMount and componentWillReceiveProps but both didn't work for me or I hadn't done them in right way.
Take your readUploadedFileAsText function outside of the if statement it is in. Then you can add your if and function call to React.useEffect which will call the function on initial load, but not on every subsequent re-render.
function Drag(props) {
const { getRootProps, getInputProps, isDragActive, isDragAccept, isDragReject, acceptedFiles } = useDropzone();
const style = useMemo(
() => ({
...baseStyle,
...(isDragActive ? activeStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {})
}),
[isDragActive, isDragReject]
);
const [data, setData] = useState(null);
const readUploadedFileAsText = acceptedFiles => {
const temporaryFileReader = new FileReader();
return new Promise((resolve, reject) => {
temporaryFileReader.onerror = () => {
temporaryFileReader.abort();
reject(new DOMException('Problem parsing input file.'));
};
temporaryFileReader.onload = () => {
resolve(parser.fromSrt(temporaryFileReader.result));
};
temporaryFileReader.readAsText(acceptedFiles);
});
};
React.useEffect(() => {
if (acceptedFiles.length === 1) {
let file = acceptedFiles[0];
readUploadedFileAsText(file).then(res => {
setData({
data: res
});
});
}
}, [acceptedFiles]);
return (
<div className='container'>
<div {...getRootProps({ style })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
<File file={data} />
{console.log(data)}
</div>
);
}
I am trying to fetch some Data from an API and put them in a flatList. Each row in the flatList has a white star button which when pressing it would make this entry added to the favorites that are saved in AsyncStorage. What I want to do is to get the keys that are saved locally and check whether this item is in the favorites list I want to show a black star. I am not able to fill the favorites array in the state.
/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow
*/
import React, { Component } from 'react';
import {
StyleSheet, Text, View, ActivityIndicator, AsyncStorage,
Image, ToastAndroid, TouchableOpacity, FlatList
} from 'react-native';
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
isLoadingData: true,
dataSourceEmpty: null,
favorites: null
}
}
componentDidMount() {
return fetch('http://barcelonaapi.marcpous.com/bicing/stations.json')
.then((response) => response.json())
.then((JsonResponse) => {
this.setState({
isLoadingData: false,
dataSourceEmpty: JsonResponse.data.bici,
favorites: null
})
})
.catch((error) => {
console.log(error);
});
}
_touchListener = (item) => {
alert("ID is:" + item.id + "\n"
+ "Latitude is: " + item.lat + "\n"
+ "Longitude is:" + item.lon)
};
makeFavorite(item) {
this.saveData(item);
ToastAndroid.show(
'This station has been added to favorites!',
ToastAndroid.SHORT
);
}
saveData(station) {
AsyncStorage.setItem(station.id + "", station.name);
}
DATABASE_getAllBusStations = () => {
return new Promise(async (resolve, reject) => {
try {
let keys = await AsyncStorage.getAllKeys();
resolve(keys)
} catch (error) {
reject(new Error('Error getting keys from AsyncStorage: ' +
error.message))
}
});
}
checkifInDatabase = async () => {
try {
var keys = await this.DATABASE_getAllBusStations();
this.setState({
isLoadingData: true,
dataSourceEmpty: null,
favorites: keys
})
} catch (error) {
console.log(error);
}
}
render() {
if (this.state.isLoadingData) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" animating />
</View>
);
}
else {
return (
<View style={styles.container}>
<Text style={styles.header}>
BARCELONA BUS STATIONS
</Text>
<FlatList
data={this.state.dataSourceEmpty}
renderItem={({ item }) => {
let source = './Assets/fav.png';
// if(this.state.favorites.includes(item.id))
// {
// source = './Assets/favblack.png';
// }
return <TouchableOpacity style={styles.item}
onPress={() => this._touchListener(item)}>
<View style={styles.row}>
<Text style={styles.textStyle}>
{item.name}
</Text>
<View style={styles.buttonStyle}>
<TouchableOpacity onPress=
{() => this.makeFavorite(item)}>
<Image
source={require(source)}
style={styles.imageStyle}
/>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
}
}
keyExtractor={(item, index) => index.toString()}
/>
</View>
);
}
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
indicator: {
alignItems: 'center',
justifyContent: 'center',
},
row:
{
flexDirection: 'row',
justifyContent: 'space-between'
},
item: {
padding: 10,
borderBottomWidth: 1,
borderBottomColor: '#eee',
backgroundColor: 'skyblue'
},
header:
{
backgroundColor: '#F03209',
textAlign: 'center',
padding: 10,
color: 'skyblue',
fontSize: 20
},
imageStyle:
{
width: 50,
height: 50
},
textStyle:
{
padding: 10
}
});
You can either nest your calls or move them to an async function. See my example below.
import * as React from 'react';
import { RouteComponentProps } from 'react-router-dom';
interface ITestComponentState {
message_1: string;
message_2: string;
message_3: string;
message_4: string;
}
export default class TestComponent extends React.Component<RouteComponentProps<{}>, ITestComponentState> {
renders = 0;
constructor(props: any) {
super(props);
this.state = {
message_1: null,
message_2: null,
message_3: null,
message_4: null
}
}
componentDidMount() {
/**Using nested fetch calls**/
fetch('api/Local/GetData')
.then(response => response.text())
.then(message_1 => {
fetch('api/Local/GetData')
.then(response => response.text())
.then(message_2 => {
this.setState({ message_1, message_2 });
});
});
/**Using an async function**/
this.getMessages().then((messages: string[]) => this.setState({ message_3: messages[0], message_4: messages[1] }));
}
async getMessages() {
let message_3 = await fetch('api/Local/GetData').then(response => response.text());
let message_4 = await fetch('api/Local/GetData').then(response => response.text());
return [message_3, message_4];
}
public render() {
const { message_1, message_2, message_3, message_4 } = this.state;
this.renders++;
return (
<div>
<h1 className="test">Message 1: {message_1}</h1>
<h1 className="test">Message 2: {message_2}</h1>
<h1 className="test">Message 3: {message_3}</h1>
<h1 className="test">Message 4: {message_4}</h1>
<h2 className="test">Renders: {this.renders}</h2>
</div>
);
}
}
This one nests a fetch inside the first fetch.
fetch('api/Local/GetData')
.then(response => response.text())
.then(message_1 => {
fetch('api/Local/GetData')
.then(response => response.text())
.then(message_2 => {
this.setState({ message_1, message_2 });
});
});
This one puts them in an async method and calls the method inside componentDidMount.
this.getMessages().then((messages: string[]) => this.setState({ message_3: messages[0], message_4: messages[1] }));
async getMessages() {
let message_3 = await fetch('api/Local/GetData').then(response => response.text());
let message_4 = await fetch('api/Local/GetData').then(response => response.text());
return [message_3, message_4];
}
the results are:
Message 1: "Hello World!"
Message 2: "Hello World!"
Message 3: "Hello World!"
Message 4: "Hello World!"
Renders: 3
what about making the componentDidMount itself async?
async componentDidMount() {
try{
const response = await axios.get('http://barcelonaapi.marcpous.com/bicing/stations.json')
if(response.data.success){
this.setState({
isLoadingData: false,
dataSourceEmpty: response.data.bici,
favorites: null
})
}catch(error){
console.log(error);
}
}