I´m trying to implement PubNub in a React Native application. The idea is to run PubNub in the background so that every 30s the location of the user is updated in a specific channel.
Here's my code. It's the export default function inside my App.js:
export default function ponte() {
const [isLoading, setIsLoading] = React.useState(false);
const [user, setUser] = React.useState(null);
const [volunteerChannel, setVolunteerChannel] = React.useState(null);
// Handle user state changes
function onAuthStateChanged(user) {
setUser(user);
if (isLoading) setIsLoading(false);
}
if (isLoading) return null;
useEffect(() => {
SplashScreen.hide();
const subscriber = auth().onAuthStateChanged(onAuthStateChanged);
return subscriber; // unsubscribe on unmount
}, []);
const authContext = React.useMemo(() => {
return {
signIn: (email, password) =>
auth()
.signInWithEmailAndPassword(email.email, password.password)
.then(res => {
setIsLoading(false);
setUser(res.user.uid);
})
,
signUp: () => {
setIsLoading(false);
setUser("test");
},
signOut: () => {
setIsLoading(false);
auth().signOut().then(() => console.log('User signed out!'));
setUser(null);
}
};
}, []);
BackgroundTimer.start();
BackgroundTimer.runBackgroundTimer(() => {
if(auth().currentUser && auth().currentUser.email /*&& !volunteerChannel*/) {
Promise.all([
fetch(API_URL + "/users?email=" + auth().currentUser.email)
])
.then(([res1]) => Promise.all([res1.json()]))
.then(([data1]) => {
if(data1[0].category === 'Voluntario') {
console.log('Volunteer channel: ' + data1[0].email);
GetLocation.getCurrentPosition({
enableHighAccuracy: true,
timeout: 15000,
})
.then(location => {
var pubnub = new PubNubReact({
publishKey: 'xxxx',
subscribeKey: 'xxxx'
});
pubnub.publish({
message: {
latitude: location.latitude,
longitude: location.longitude
},
channel: data1[0].email
});
})
.catch(error => {
const { code, message } = error;
console.warn(code, message);
})
} else {
console.log(data1[0]);
}
})
.catch((error) => console.log(error))
}
}, 30000);
BackgroundTimer.stop();
return(
<AuthContext.Provider value={authContext}>
<NavigationContainer>
<RootStackScreen user={user} />
</NavigationContainer>
</AuthContext.Provider>
);
}
I'm worried about two things with this code:
The first one is the amount of pubnub transactions registered: Apparently I have over 3.5K transactions and I barely used the app in the iOS simulator.
The second is I get a warning: CANCELLED Location cancelled by another request. I'm not so sure where that comes from, maybe it comes from GetLocation.getCurrentPosition so it might not be related to PubNub.
So my question is: Am I using the right approach to use PubNub in React Native? If not, what can I do to make it better?
Related
Okay, there's this simple REACTJS app, where firebase is used.
There once you login everything works fine except when you hit the refresh icon. The moment you do it, it redirects you to the previous place where you were asked to login. That's the problem that this newly-born coder is trying to solve!
I can give you following snippets of code:
This is of the landing page
function Landing() {
const [{ }, dispatch] = useStateValue();
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.NONE)
// .then(function () {
// console.log("successfully set the persistence");
// return firebase.auth().signInWithPopup(provider);
// })
.catch(function (error) {
console.log("failed to ser persistence: " + error.message)
});
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log('user is logged in');
} else {
console.log('user is logged out now')
}
});
const signIn = () => {
auth
.signInWithPopup(provider)
.then((result) => {
dispatch({
type: actionTypes.SET_USER,
user: result.user
})
}).catch((error) => alert(error.message))
}
reducer.js snippet
export const initialState = {
user: null,
}
export const actionTypes = {
SET_USER: 'SET_USER',
LOGOUT_USER: 'LOGOUT_USER'
}
const reducer = (state, action) => {
console.log(action)
switch (action.type) {
case actionTypes.SET_USER:
return {
...state,
user: action.user,
}
case actionTypes.LOGOUT_USER:
return {
...state,
user: null,
}
default:
return state;
This is of firebase.js
Yes, Google Authentication is what's being used here
import firebase from 'firebase';
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
//config
};
// const user = firebase.auth().currentUser;
// console.log(user);
const firebaseApp = firebase.initializeApp(firebaseConfig)
const db = firebaseApp.firestore();
const storage = firebase.storage();
const auth = firebaseApp.auth();
const provider = new firebase.auth.GoogleAuthProvider();
export default db;
export { auth, provider, storage }
Finally here is of the app.js
function App() {
const [{ user }, dispatch] = useStateValue();
console.log(user);
return (
<div className="app">
{!user ? (
<Landing />
) : (
<App />
)
</div>
Your attention to this matter is greatly appreciated!
Oh by the way this following question is also related to this. It might help you to get a better idea of this issue. So make sure to take a look at that as well!
How can you persist a logged-in user with firebase?
Thanks again!
sample code. work
import firebase from 'firebase/app';
import 'firebase/auth';
import { useEffect, useState } from 'react';
import firebaseConfig from './firebase-config';
const firebaseApp = firebase.initializeApp(firebaseConfig);
const googleProvider = new firebase.auth.GoogleAuthProvider();
firebaseApp.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
.then(function () {
// return firebaseApp.auth().signInWithPopup(googleProvider)
})
.catch(function (error) {
console.log(error)
});
function App() {
const [user, setUser] = useState(null)
useEffect(() => {
firebaseApp.auth().onAuthStateChanged((res) => {
console.log("onAuthStateChanged", res)
if (res) {
setUser(res)
// console.log('user is logged in', user);
} else {
setUser(null)
// console.log('user is logged out now')
}
});
}, [])
const signInWithGoogle = (e) => {
firebaseApp.auth()
.signInWithPopup(googleProvider)
.then((result) => {
// console.log(result)
// setUser(result.additionalUserInfo)
}).catch(err => {
// console.log(err)
})
}
const signOut = (e) => {
firebaseApp.auth().signOut()
}
return (
<div>
<h1>Firebase Authentication</h1>
{
user
? (
<div>
<p>Hello, {user.displayName}</p>
<button onClick={signOut}>Sign out</button>
</div>
)
: (
<div>
<p>Please sign in.</p>
<button onClick={signInWithGoogle}>Sign in with Google</button>
</div>
)
}
</div>
);
}
export default App;
I got stuck with the following and haven't found any answer after a lot of research.
What I want to do: simply getting users inluding their images from a firestore-DB with react and the useeffect-hook and displaying them.
The DB-structure looks as follows:
https://i.stack.imgur.com/sDcrv.png
So the pictures are a subcollection of the users-collection.
After getting the users from the users-collection, I'm doing a second request for adding the users images to this specific user using Object.assign. After every forEach-run over the users-collection I'm setting the users-array with setUsers((oldUsers) => [...oldUsers, currentUser]);. Logging the users-array shows uses INCLUDING their images.
The problem: When trying to render the images, they are always undefined.
Workaround: Pressing a button that calls a function for re-setting the users:
const reRenderUsers = () => {
if (userDataLoaded === false) {
setUserDataLoaded(true);
}
const copy = [...users];
setUsers(copy);
};
^ This solves the problem and all images where shown.
Question: Is there any possibility showing the images instantly without the need of "re-rendering" the users? Am I using the useEffect-hook wrong for example? I'm thankful for any advice. Many thanks in advance!
Here the full code:
const [users, setUsers] = useState([]);
const [userDataLoaded, setUserDataLoaded] = useState(false);
useEffect(() => {
const unsubscribe = database.collection("users").onSnapshot((snapshot) => {
snapshot.forEach((doc) => {
const currentUser = {
id: doc.id,
...doc.data(),
};
database
.collection("users")
.doc(currentUser.id)
.collection("pictures")
.get()
.then((response) => {
const fetchedPictures = [];
response.forEach((document) => {
const fetchedPicture = {
id: document.id,
...document.data(),
};
fetchedPictures.push(fetchedPicture);
});
currentUser.pictures = [];
Object.assign(currentUser.pictures, fetchedPictures);
})
.catch((error) => {
console.log(error);
});
setUsers((oldUsers) => [...oldUsers, currentUser]);
});
});
return () => {
unsubscribe();
};
}, []);
const reRenderUsers = () => {
if (userDataLoaded === false) {
setUserDataLoaded(true);
}
const copy = [...users];
setUsers(copy);
};
return (
<div>
{!userDataLoaded ? (
<button onClick={reRenderUsers}> load users </button>
) : null}
{users.map((user, index) => (
<div key={user.id}>
{user.pictures && <img src={user.pictures[0].imageUrl}></img>}
</div>
))}
</div>
);
}
export default User;
This is because you are calling setUser before the firebase response completes the callback chain. You need to update the state right after the loop inside the success callback completed. I have updated useEffect to update it right after the callback
useEffect(() => {
const unsubscribe = database.collection("users").onSnapshot((snapshot) => {
snapshot.forEach((doc) => {
const currentUser = {
id: doc.id,
...doc.data(),
};
database
.collection("users")
.doc(currentUser.id)
.collection("pictures")
.get()
.then((response) => {
const fetchedPictures = [];
response.forEach((document) => {
const fetchedPicture = {
id: document.id,
...document.data(),
};
fetchedPictures.push(fetchedPicture);
});
currentUser.pictures = fetchedPictures;
setUsers((oldUsers) => [...oldUsers, currentUser]);
})
.catch((error) => {
console.log(error);
});
//dont need this here
//setUsers((oldUsers) => [...oldUsers, currentUser]);
});
});
return () => {
unsubscribe();
};
}, []);
Good Luck
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.
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);
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;