how to use async function with custom hooks inside useEffects - javascript

i want the get the moralis useTokenPrice to fetch an updated price after every five seconds, but from the rules of hook a react hook cannot be used inside useEffects.
how do i go about it.
my code
function SpeedPrice(props) {
const [price, setPrice] = useState({
symbol: "",
token_address: "",
price: "",
});
const MINUTE_MS = 5000;
const address = props.address;
const symbol = props.symbol;
async function GetPrice() {
const result = await useTokenPrice({ // moralis hook
chain: "eth",
address: address,
});
const usdPrice = result.data.formattedUsd;
setPrice({ symbol: symbol, token_address: address, price: usdPrice });
}
// GetPrice(); infinite loop
useEffect(() => {
const interval = setInterval(() => {
console.log("call getprice");
// GetPrice() error! React Hooks must be called in a React function component or a custom React Hook function
}, MINUTE_MS);
return () => clearInterval(interval);
}, []);
return price.price;
}
what i have done
useEffect(() => {
const interval = setInterval(() => {
// moved the function inside useEffects
async function GetPrice() {
const result = await useTokenPrice({ // moralis hook
chain: "eth",
address: address,
});
const usdPrice = result.data.formattedUsd;
setPrice({ symbol: symbol, token_address: address, price: usdPrice });
}
GetPrice();
}, MINUTE_MS);
return () => clearInterval(interval);
}, []);

You can use hooks only in top level. But in your case
useTokenPrice return fetch function which you can use everywhere:
const {fetchTokenPrice/*πŸ‘ˆ*/, data /*πŸ‘ˆ*/} = useTokenPrice({
chain: 'eth',
address: address
});
useEffect(() => {
const interval = setInterval(async () => {
console.log('call getprice');
await fetchTokenPrice(address); // πŸ‘ˆ
}, MINUTE_MS);
return () => clearInterval(interval);
}, []);
const usdPrice = data.formattedUsd; // πŸ‘ˆ
return data.isLoading || data.isFetching ? 'Loading...' : usdPrice;

Related

Not getting updated state value in socket callback react native

I have listend an event in customhook and when that event works, I have to do some logic there with state.but now I only get empty array every time that event callback works.
const useChatHistoryList = () => {
const sk = useSocket();
const [chatList, setChatList] = useState([]);
const [end, setEnd] = useState(true);
useEffect(() => {
sk.emit('chatlist');
}, [start]);
useEffect(() => {
const onChatListReceived = data => {
const _data = JSON.parse(data);
setHistoryLoading(false);
setChatList(_data);
};
const onChatListToUpdateReceived = data => {
const _data = JSON.parse(data);
console.log(chatList);//getting only empty array everytime
};
sk.on('chatlist', onChatListReceived);
sk.on('chatlistToUpdate', onChatListToUpdateReceived);
return () => {
sk.off('chatlistToUpdate');
sk.off('chatlist');
};
}, []);
return { chatList,end};
};
Try to log your data first to make sure the data is there, then set your state with the data.
const [state, setState]= useState([]);
const _onReceived = (data) => {
// Here is your data from socket
console.log(data);
// Then set state value with data
setState(data);
}
useEffect(()=>{
// Init socket listener
socket.on("event", _onReceived);
}, []);
// This effect will runs everytime state value is set (including when setting default value)
useEffect(()=>{
// Actual 'state' value
console.log('State value: ', state);
}, [state]);
==========================
Edit, related to your updated codes in the question
Your onChatListToUpdateReceived function brings empty default value to the listener even later when it’s updated, your listener will still recognize chatList value as an empty string. Try to move out onChatListToUpdateReceived outside useEffect.
const onChatListToUpdateReceived = data => {
const _data = JSON.parse(data);
console.log(chatList);//getting only empty array everytime
};
useEffect(() => {
const onChatListReceived = data => {
const _data = JSON.parse(data);
setHistoryLoading(false);
setChatList(_data);
};
sk.on('chatlistToUpdate', onChatListToUpdateReceived);
return () => {
sk.off('chatlistToUpdate');
sk.off('chatlist');
};
}, []);
useEffect(() => {
sk.off('chatlistToUpdate');
sk.on('chatlist', onChatListReceived);
}, [chatList]);
I have not used socket.io before but this is what I meant by asynchronous update. From your code, it looked to me like your callback is getting called before the state is updated. So to solve this, I added a useEffect() with chatList as a dependency so that callback gets called every time chatList gets updated. I hope this makes sense.
const useChatHistoryList = () => {
const sk = useSocket();
const [chatList, setChatList] = useState([]);
const [end, setEnd] = useState(true);
const onChatListReceived = data => {
const _data = JSON.parse(data);
setHistoryLoading(false);
setChatList(_data);
};
const onChatListToUpdateReceived = data => {
const _data = JSON.parse(data);
console.log(chatList); //getting only empty array everytime
};
useEffect(() => {
sk.on('chatlist', onChatListReceived);
sk.on('chatlistToUpdate', onChatListToUpdateReceived);
return () => {
sk.off('chatlistToUpdate');
sk.off('chatlist');
};
}, []);
// Emit chatlistToUpdate whenever chatList is updated
useEffect(() => {
sk.emit('chatlistToUpdate');
}, [chatList]);
return {
chatList,
end
};
};

firebase data not being updated with use effect

hi i have to refresh my page to see the effect of the person adding an event to the calendar:
my code is
const handleDateClick = async (DateClickArg) => {
if (DateClickArg) {
const title = prompt("Enter title", DateClickArg.dateStr); // allows user to put a title in
// making object
const event = {
title: title ? title : DateClickArg.dateStr,
start: DateClickArg.date,
allDay: true,
};
allEvents.push(event);
const db = fire.firestore();
let currentUserUID = fire.auth().currentUser.uid;
const doc = await fire
.firestore()
.collection("userCalendar")
.doc(currentUserUID)
.get();
db.collection("userCal/" + currentUserUID + "/activities").add({ event });
}
};
and my getuserinfo is:
const getUserInfo = async () => {
let currentUserUID = fire.auth().currentUser.uid;
const qSnap = await fire
.firestore()
.collection("userCal")
.doc(currentUserUID)
.collection("activities")
.get();
const data = [];
data = qSnap.docs.map((d) => ({
id: d.id,
title: d.data().event.title,
start: d.data().event.start.toDate(),
allDay: d.data().event.allDay,
...d.data(),
}));
//setData(data)
console.log(data);
setData([...data]);
};
useEffect(() => {
let mounted = false;
if (!mounted) {
getUserInfo();
}
return () => {
mounted = true;
};
}, []);
where am i going wrong with my use effect? is there a way for the data to update in the browser once its added to firebase? i am using react full calendar
Using get() only returns a point-in-time snapshot of your data. If you want to listen for realtime updates, use .onSnapshot() instead.
You'll also need to make sure you unsubscribe from updates when your component is cleaned up
useEffect(() => {
const currentUserUID = fire.auth().currentUser.uid;
return fire
.firestore()
.collection("userCal")
.doc(currentUserUID)
.collection("activities")
.onSnapshot(({ docs }) => {
setData(
docs.map((doc) => {
const data = doc.data();
return {
id: doc.id,
title: data.event.title,
start: data.event.start.toDate(),
allDay: data.event.allDay,
...data,
};
})
);
});
}, []);
.onShapshot() returns an unsubscribe function so returning that from your effect hook will run it when your component is unmounted.
Assuming your firebase call is ok, there is an error inside your useEffect call. You are setting the mounted variable wrong, it is supposed to be false when your component is destroyed and true after your component is rendered. Also, to avoid unexpected behaviors I highly recommend using the useRef hook to check that.
function Component() {
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
if (isMounted) {
getUserInfo();
}
return () => { isMounted.current = false }
}, []);
...
}
export default Component;

"too much recursion" while updating params(setData) into Firebase RTDB through setInterval and getData

I'm new to react native. I have a problem while updating data(latitude, longitude, timestamp) for each user(member) to Firebase database realtime, I get endless loops
The error I have "too much recursion"
firebase.config.ts
const firebaseConfig = {
...
};
export const getCurrentLocation = (phoneNumber: number, setData: (locationParams: any) => void) => {
const db = getDatabase();
const reference = ref(db, 'members/' + phoneNumber);
onValue(reference, (snapshot) => {
const data = snapshot.val();
setData(data);
})
};
export const setCurrentLocation = (phoneNumber: number, latitude: number, longitude: number, timestamp: number) => {
const db = getDatabase();
const reference = ref(db, 'members/' + phoneNumber);
set(reference, {
latitude: latitude,
longitude: longitude,
timestamp: timestamp,
}).then(() => console.log('setCurrentLocation to mainUser/firebase.config'));
};
const app = initializeApp(firebaseConfig);
memberList.tsx
const [userPhoneNumber, setUserPhoneNumber] = useState('0');
const members = useSelector((state: any) => state.members);
//get user's phoneNumber
const auth = getAuth();
useEffect( ()=> {
onAuthStateChanged(auth, (user) => {
if (user) {
setUserPhoneNumber(user.email.replace(/\D/gi, '') || '');
} else {
console.log('logged out')
}
});
}, [])
useEffect(() => {
const timer = setInterval( () => {
members.map(function (el) {
getCurrentLocation(el.phoneNumber, (locationParams: any) => {
let timestamp = new Date().getTime()/1000;
setCurrentLocation(el.phoneNumber, locationParams.latitude, locationParams.longitude, timestamp)
})
})
}, 10000);
return () => clearInterval(timer);
}, []);
This looks wrong:
const timer = setInterval( () => {
members.map(function (el) {
getCurrentLocation(el.phoneNumber, (locationParams: any) => {
let timestamp = new Date().getTime()/1000;
setCurrentLocation(el.phoneNumber, locationParams.latitude, locationParams.longitude, timestamp)
})
})
}, 10000);
Since your getCurrentLocation attaches a listener with onValue, just doing that once for each user will execute your setData call right away for the current value in the database, and again whenever that user's data is updated. Combining onValue with an interval timer is an antipattern in Firebase, the database already calls back to your code when the data changes. Right now you attach a new listener for each user each second, so if there's an update to a user's data after more than a few seconds, your callback (and setData) will be called way more often than necessary.
In Firebase, attach your listeners once (typically when the page loads) and let them continue to work until the page is unloaded (or other changes are needed in the listeners).
So if you remove the setInterval from the code, you should be getting better results.

Why is my UseEffect for making requests to Spotify API giving an error 429?

I am currently making a Spotify clone which gives user a preview of the song. The problem occurs when I am making many different api requests. When there are more than one requests on the page, it throws a 429 error(making too many requests at once).
Please read through the whole question as I have mentioned the steps I have taken to fix this below.
Profile.js
const { api, refreshableCall } = useSpotify()
const [error, setError] = useState(null)
const [userName, setUserName] = useState("")
const [userFollowers, setUserFollowers] = useState("")
const [userImage, setUserImage] = useState([])
const [userLink, setUserLink] = useState("")
const [userId, setUserId] = useState("")
const [userFollowing, setUserFollowing] = useState("")
const [userTopArtists, setUserTopArtists] = useState([])
const [userTopSongs, setUserTopSongs] = useState([])
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMyTopTracks({
limit: 10,
time_range: "long_term"
}))
.then((res) => {
if (disposed) return
setUserTopSongs(res.body.items)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserTopSongs([])
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMe())
.then((res) => {
if (disposed) return
var data = res.body
setUserName(data.display_name)
setUserImage(data.images)
setUserFollowers(data.followers["total"])
setUserLink(data.external_urls.spotify)
setUserId(data.id)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserName("")
setUserImage([])
setUserFollowers("")
setUserLink("")
setUserId("")
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getFollowedArtists())
.then((res) => {
if (disposed) return
var data = res.body
var artists = data.artists
setUserFollowing(artists.total)
})
.catch((err) => {
if (disposed) return
setUserFollowing([])
setError(err)
});
return () => disposed = true
})
useEffect(() => {
let disposed = false
refreshableCall(() => api.getMyTopArtists({
limit: 10,
time_range: "long_term"
}))
.then((res) => {
if (disposed) return
var data = res.body
var artists = data.items
setUserTopArtists(artists)
setError(null)
})
.catch((err) => {
if (disposed) return
setUserTopArtists([])
setError(err)
});
return () => disposed = true
})
SpotifyContext.js
import React, { useState, useEffect, useContext } from "react"
import axios from "axios"
import SpotifyWebApi from 'spotify-web-api-node';
const spotifyApi = new SpotifyWebApi({
clientId: 1234567890,
});
export const SpotifyAuthContext = React.createContext({
exchangeCode: () => { throw new Error("context not loaded") },
refreshAccessToken: () => { throw new Error("context not loaded") },
hasToken: spotifyApi.getAccessToken() !== undefined,
api: spotifyApi
});
export const useSpotify = () => useContext(SpotifyAuthContext);
function setStoredJSON(id, obj) {
localStorage.setItem(id, JSON.stringify(obj));
}
function getStoredJSON(id, fallbackValue = null) {
const storedValue = localStorage.getItem(id);
return storedValue === null
? fallbackValue
: JSON.parse(storedValue);
}
export function SpotifyAuthContextProvider({ children }) {
const [tokenInfo, setTokenInfo] = useState(() => getStoredJSON('myApp:spotify', null))
const hasToken = tokenInfo !== null
useEffect(() => {
if (tokenInfo === null) return;
// attach tokens to `SpotifyWebApi` instance
spotifyApi.setCredentials({
accessToken: tokenInfo.accessToken,
refreshToken: tokenInfo.refreshToken,
})
// persist tokens
setStoredJSON('myApp:spotify', tokenInfo)
}, [tokenInfo])
function exchangeCode(code) {
return axios
.post("http://localhost:3001/login", {
code
})
.then(res => {
// TODO: Confirm whether response contains `accessToken` or `access_token`
const { accessToken, refreshToken, expiresIn } = res.data;
// store expiry time instead of expires in
setTokenInfo({
accessToken,
refreshToken,
expiresAt: Date.now() + (expiresIn * 1000)
});
})
}
function refreshAccessToken() {
const refreshToken = tokenInfo.refreshToken;
return axios
.post("http://localhost:3001/refresh", {
refreshToken
})
.then(res => {
const refreshedTokenInfo = {
accessToken: res.data.accessToken,
// some refreshes may include a new refresh token!
refreshToken: res.data.refreshToken || tokenInfo.refreshToken,
// store expiry time instead of expires in
expiresAt: Date.now() + (res.data.expiresIn * 1000)
}
setTokenInfo(refreshedTokenInfo)
// attach tokens to `SpotifyWebApi` instance
spotifyApi.setCredentials({
accessToken: refreshedTokenInfo.accessToken,
refreshToken: refreshedTokenInfo.refreshToken,
})
return refreshedTokenInfo
})
}
async function refreshableCall(callApiFunc) {
if (Date.now() > tokenInfo.expiresAt)
await refreshAccessToken();
try {
return await callApiFunc()
} catch (err) {
if (err.name !== "WebapiAuthenticationError")
throw err; // rethrow irrelevant errors
}
// if here, has an authentication error, try refreshing now
return refreshAccessToken()
.then(callApiFunc)
}
return (
<SpotifyAuthContext.Provider value={{
api: spotifyApi,
exchangeCode,
hasToken,
refreshableCall,
refreshAccessToken
}}>
{children}
</SpotifyAuthContext.Provider>
)
}
Errors
Without the dependency, it keeps cycling and firing off requests, likely hundreds per second. (Error 429)
With the dependency, it seems the Access Token is being ignored or sidestepped. (Error: WebApiAuthentication - No token provided)
What I have tried to do ?
I tried to implement all the requests in a single useEffect, still getting the errors.
Calling useEffect with dependency array and without.
Link to the Github Repo
https://github.com/amoghkapoor/spotify-clone
status 429 means you have made too many calls in a specific time window.
you are therefore banned for this specific time window.
try waiting a bit before retrying.
did you try :
useEffect(..., [])
this guaranties it will be run only once.
None of your useEffect calls are using a dependency array, remember if useEffect is called without any dependencies it goes into an infinite loop. Either find what dependency or state change should re-run the useEffect hook and include it in the dependency array:
useEffect(() => { /* your logic */ }, [dependencies])
or if there are no dependencies simply fire it once the component mounts:
useEffect(() => { /* your logic */ }, [])

How to deal with nested useEffect?

Recently started using Hooks and, as cook as they are, they are giving me a bit of a headache.
I have a custom useFetch() hook that deals with fetching data from the API.
I also have a component where I need to use useFetch a few times and the results must be passed from one to another.
E.g.:
const ComponentName = () => {
const { responseUserInfo } = useFetch('/userinfo')
const { responseOrders } = useFetch(`/orders?id=${responseUserInfo.id}`)
const { isOrderRefundable } = useFetch(`/refundable?id={responseOrders.latest.id}`)
return <div>{isOrderRefundable}</div>
}
So, how do I actually "cascade" the hooks without creating 3 intermediate wrappers? Do I have to use HoC?
Your hook could return a callback, that when called does the API call:
const [getUserInfo, { userInfo }] = useFetch('/userinfo');
const [getOrders, { orders }] = useFetch(`/orders`)
const [getOrderRefundable, { isOrderRefundable }] = useFetch(`/refundable`);
useEffect(getUserInfo, []);
useEffect(() => { if(userInfo) getOrders({ id: userInfo.id }); }, [userInfo]);
useEffect(() => { if(orders) getOrderRefundable({ id: /*..*/ }); }, [orders]);
But if you always depend on the whole data being fetched, I'd just use one effect to load them all:
function useAsync(fn, deps) {
const [state, setState] = useState({ loading: true });
useEffect(() => {
setState({ loading: true });
fn().then(result => { setState({ result }); });
}, deps);
return state;
}
// in the component
const { loading, result: { userInfo, orders, isRefundable } } = useAsync(async function() {
const userInfo = await fetch(/*...*/);
const orders = await fetch(/*...*/);
const isRefundable = await fetch(/*...*/);
return { userInfo, orders, isRefundable };
}, []);

Categories

Resources