I am creating a chat app in React using socket.io. I don't need the following useEffect() to run more than once, and what this warning message is asking me to resolve would conflict with my intention of setting the socket message handler only once:
React Hook useEffect has a missing dependency: 'messages'. Either include it or remove the dependency array. You can also do a functional update 'handleMessages(m => ...)' if you only need 'messages' in the 'handleMessages' call.eslintreact-hooks/exhaustive-deps
export default function Group(props) {
const [_broadcastOptions, handleBroadcastOptions] = useState(false);
const [messages, handleMessages] = useState([]);
const textValue = useRef('');
const bottomScrollRef = useRef();
const socket = useRef();
useEffect(()=>{
console.log('bruh');
socket.current = io('http://0.0.0.0:3001');
socket.current.on('connect', ()=> {
console.log('socket connection status: ' + socket.current.connected);
socket.current.emit('enter-room', getRoomId(window.location.pathname));
});
socket.current.on('message', m => {
console.log('message received: ' + JSON.stringify(m));
handleMessages([...messages, m]);
});
}, []);
return (...);
}
Edit: I've tried passing in a callback in my handleMessages function (functional updates as react calls them) but I still get the warning.
[![enter image description here][2]][2]
If you use the callback for handleMessages instead, there's no need to use the outer messages identifier (which would be in an old closure anyway).
socket.current.on('message', m => {
console.log('message received: ' + JSON.stringify(m));
handleMessages(messages => [...messages, m]);
});
In this case, you should using a callback in the handleMessages to update state. You can read more in here: https://reactjs.org/docs/hooks-reference.html#functional-updates
handleMessages((preMessage) => [...preMessage, m]);
If you're sure about this, add // eslint-disable-next-line before the line that shows the error.
Related
Here is the new script with the find function which allows me to identify a single element of the array for sure but there is still a small problem. As you can see it's my const crypto which contains the data I want to display on the front end. However when I want to call crypto at the return level the const is not recognized.
Hello again,
I updated the script now it works I can display on the front end the data I want however I have the impression that the request to the api is executed several times when I would like there to be only one request
I put below a screen of the console.log of my script.
As you can see the data requested is displayed first as undefined then it is displayed several times, then I put blocked due to the too large number of requests made in little time
Thanks for your help
How do I make my requests :
import { useState, useEffect } from "react";
function App() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null)
useEffect(() => {
fetch("http://localhost:4500/coingeckotest")
.then(response => {
if (response.ok) {
return response.json()
}
throw response;
})
.then(data => {
setData(data)
})
.catch(error => {
console.error("Error fetching data: ", error)
setError(error)
})
.finally(() => {
setLoading(false)
})
}, [])
const uniswap = data&&data[0].tickers?.find(donne =>
donne.trade_url === 'https://app.uniswap.org/#/swap?
inputCurrency=0x2260fac5e5542a773aa44fbcfedf7c193bc2c599&
outputCurrency=ETH')
const uniprice = uniswap?.converted_last?.usd
const sushiswap = data&&data[1].tickers?.find(donne =>
donne.trade_url === 'https://app.sushi.com/swap?
inputCurrency=0x2260fac5e5542a773aa44fbcfedf7c193bc2c59
9&outputCurrency=ETH')
const sushiprice = sushiswap?.converted_last?.usd
const curvefinance = data&&data[2].tickers?.find(donne =>
donne.base === 'DAI')
const curveprice = curvefinance?.converted_last?.usd
const quickswap = data&&data[3].tickers?.find(donne =>
donne.trade_url === 'https://quickswap.exchange/#/swap?
inputCurrency=0x0d500b1d8e8ef31e21c99d1db9a6444d3adf127
0&outputCurrency=0xbbba073c31bf03b8acf7c28ef0738decf369
5683')
const quickprice = quickswap?.converted_last?.usd
console.log(uniprice)
console.log(sushiprice)
console.log(curveprice)
console.log(quickprice)
if (loading) return "Loading..."
if(error) return "Error !"
return (
<>
</>
)
}
export default App;
Thank you in advance for your answers
You can use the Array.find method to find out the first entry that matches a particular coin_id. The code for that would be:
const desiredCoinID = "something"; (determined by some user input)
const desiredCoinObject = data.find(coin => coin.coin_id === desiredCoinID);
const priceInUSD = desiredCoinObject?.tickers?.converted_last?.usd;
——-Updated Answer——-
Hi, this is my answer to your updated question. const crypto that you use is available only within the scope of the callback of the useEffect function, which is why you cannot see the results on your screen. There are two ways you can go about it:
First, you can declare crypto as a let outside the useEffect and then update it inside your useEffect. That way your crypto will have global scope. But there is a better way to do this, which is to use the useState hook.
You can declare a crypto, setCrypto using useState and then use the setter to update the value if crypto inside useEffect after the data fetching is over. Let me know if you need help writing code.
wondering if anyone can assist me in this matter. I'm following the documentation for https://rnfirebase.io/firestore/usage. it does not work for my use case for some reason.
I just need to set the data, which it works and then read it back so i can push it onto my state and i'll render it.
I just can't read the data back properly. This addItemFunction is trigger when when user click on a button to add.
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
//RETURN SNAPSHOT NOT WORKING
console.log('user_added');
firestore().collection(userEmail).doc(final).onSnapshot(documentSnapshot =>{
console.log("here" + documentSnapshot.data());
});
}
Thanks for your time.
If you are using react with hooks I would suggest you put the onSnapshot listener in a useEffect hook:
useEffect(() => {
const unsubscribe = firestore
.collection(collectionName)
.doc(docId)
.onSnapshot(
(documentSnapshot) => {
const document = documentSnapshot.data();
console.log(document)
},
(error: Error) => {
throw error;
}
);
return () => unsubscribe();
}, [ docId, collectionName]);
this approach will separate concerns and the snapshots will run every time there is a change on the document, then where I put the console.log you could set the document to state.
Another approach will be to use get() instead of onSnapshot like:
const addItemFunction = async (numb,exercise) =>{
firestore().collection(userEmail).get().then((snap) =>{
if(!snap.empty){
var finalID = uuid.v4();
firestore().collection(userEmail).doc(final).update({
[finalID]:{
exe:[exercise],
num:[numb],
}
}).then(() =>{
console.log('user_added');
firestore().collection(userEmail).doc(final).get().then(() => {
console.log("here" + documentSnapshot.data());
})
}
}
}
this approach will not subscribe to changes and it will return the new updated document every time you call the addItemFunction
I was working on an app based on webRTC API , and working with useRef() hook in react, I got an error
Error: TypeError: myVideo.current is undefined
ContextProvider SocketContext.js:27
promise callback*ContextProvider/< SocketContext.js:23
React 5
unstable_runWithPriority scheduler.development.js:468
React 3
workLoop scheduler.development.js:417
flushWork scheduler.development.js:390
performWorkUntilDeadline scheduler.development.js:157
js scheduler.development.js:180
js scheduler.development.js:645
Webpack 21
and the code for context is
const myVideo = useRef();
//const myVideo = useRef(null); tried this also but not solved
const userVideo = useRef();
const connectionRef = useRef();
useEffect(() => {
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((currentStream) => {
setStream(currentStream);
myVideo.current.srcObject = currentStream;
})
.catch((err) => {
console.log("Error ", err);
})
socket.on('me', (id) => setMe(id));
socket.on('callUser', ({ from, name: callerName, signal }) => {
setCall({ isReceivingCall: true, from, name: callerName, signal });
});
}, []);
I've tried adding myVideo || myVideo.current is useRef() dependency list.
Looking a bit more into it, it seems like you can't properly use React-controlled refs within useEffect. A solution described here and several answers on this question might help you further.
In short, your useEffect gets called before the reference gets set. Instead, use a callback as reference instead:
const myVideo = useCallback(video => {
if (!video) return; // video object no longer available
// You now have the video object
// either set the `srcObject` here, or store `video` to set it later
}, []);
// somewhere else
return <video ref={myVideo} ... />;
I have an Effect hook in a React component that sets up and initialises a class that I use to communicate with a backend server:
const SignalProvider = ({url, children}) => {
let [sigErr, setSigErr] = useState("")
let [token, setToken] = useContext(TokenContext)
let [signaller, setSignaller] = useState()
useEffect(() => {
if (!signaller) {
const s = new Signaller(url, (err) => setSigErr(err))
setSignaller(s)
}
if (token && signaller) {
signaller.setToken(token)
signaller.setSetTokenCallback(setToken) // Adding this line causes an infinite loop
signaller.connect()
}
}, [url, token, signaller, setToken, authError])
...
}
However, adding the line signaller.setSetTokenCallback(setToken) causes an infinite loop of re-rendering. Without this line it works as expected.
All setSetTokenCallback does is:
setTokenCallback(f) {
this.setTokenCallback = f
}
Which I don't think should matter.
Whats the best way to prevent the loop?
Have you tried using 'useCallback()' hook?
const initializeClass = useCallback(() => {
if (!signaller) {
const s = new Signaller(url, (err) => setSigErr(err))
setSignaller(s)
}
if (token && signaller) {
signaller.setToken(token)
signaller.setSetTokenCallback(setToken)
signaller.connect()
}, [url, token, signaller, setToken, authError])
useEffect(() => {
initializeClass()
}, [bla bla all dependencies needed])
I think your culprit is in these lines
signaller.setToken(token) // first possible culprit
signaller.setSetTokenCallback(setToken)
Since you are setting the token inside the useEffect and your useEffect depends on the token value for a re-evaluation again. If the token value is different than the previous one then it will make the useEffect to re-render.
[url, token, signaller, setToken, authError] // second possible culprit
Also, you should remove object or array type variables from the condition array [url, token, signaller, setToken, authError] or stringify them or find some way to compare, as objects and arrays can't be compared directly. They will always return false if directly compared. Thus, your useEffect will re-run.
Background
I'm building an app which displays a number of stores in the home screen. They are shown in a carousel which is filled up with information from a Firestore Collection and Firebase Storage. The user can navigate into each store by pressing on them. The Home Screen display works just fine every single time, but when navigating to one store components come back as undefined. This is the way I'm fetching the data:
export default function StoreDetailMain ({route}) {
const { storeId } = route.params
const [store, setStore] = useState()
useEffect(() => {
const fetchQuery = async () => {
const storeData = await firebase.firestore()
.collection('stores/')
.doc(storeId)
.get()
.then(documentSnapshot => {
console.log('Store exists: ', documentSnapshot.exists);
if (documentSnapshot.exists) {
console.log('Store data: ', documentSnapshot.data());
setStore(documentSnapshot.data())
console.log(documentSnapshot.data())
}
});
}
fetchQuery()
}, [storeId])
Then I'm rendering the information within tags as in <Text>{store.value}</Text>.
Problem
Navigating once to the store will always return a Component Exception: undefined is not an object (evaluating 'store.value'). However if I cut the "{store.value}" tags it works just fine. Then I can manually type them in again and they render perfectly. Once I go back to the Home Screen and try to go into another store I have to do it all again. Delete the calls for information within the return(), save the code, reload the app and type them in again.
What I have tried
Sometimes, not always, Expo will give me a warning about not being able to perform a React state update on an unmounted component. I thought this might be the problem so I gave it a go by altering my useEffect method:
export default function StoreDetailMain ({route}) {
const { storeId } = route.params
const [store, setStore] = useState()
useEffect(() => {
let mounted = true;
if(mounted){
const fetchQuery = async () => {
const storeData = await firebase.firestore()
.collection('stores/')
.doc(storeId)
.get()
.then(documentSnapshot => {
console.log('Store exists: ', documentSnapshot.exists);
if (documentSnapshot.exists) {
console.log('Store data: ', documentSnapshot.data());
setBar(documentSnapshot.data())
console.log(documentSnapshot.data())
}
});
}
fetchQuery()
}
return () => mounted = false;
}, [storeId])
This would not solve the issue nor provide any variation.
Question
Is this due to the unmounting/mounting of components? If so, wouldn't the useEffect method take care of it? If anyone could provide an explanation/solution it would be very much appreciated.
Thanks in advance.
Edit 1:
When the application fails to render the information, it doesn't print into the console the document snapshot. When it can render the data, it does log it. Thus the change in title.
try giving it a initial value
const [ store, setStore ] = useState({value: ''})
or render it conditionally
{ store?.value && <Text>{store.value}</Text> }
secondly, route.params is defined? When you switching screens, did u make sure u pass the params? Switching from stack navigator to tab navigator for example, may drop the params.