Can't stop multiple calls to external API or get data to render in browser - reactjs

I'm trying to add the user's local weather to my site using openweathermap.org's free API. The API call works; I get want I want back. But then it keeps calling the API hundreds of times. I also am having problems getting the data to render in the browser, which I think is an asynchronous Javascript problem. I have tried moving the function that triggers the fetch call, putting it in a useEffect(), using a setTimeout() and nothing works. When it's in the useEffect(), I get an error pointing to an issue 'reading 'latitude' in the fetch call.
So how do I solve those issues? And is there a way to store the latitude and longitude in a useState()?
FETCH CALL
export const getWeather = (coords, API) => {
return fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${coords.latitude}&lon=${coords.longitude}&units=metric&appid=${API}`)
.then(res => res.json())
}
CODE
export const Weather = () => {
const [weather, setWeather] = useState({})
const APIkey = "(redacted)"
const { coords, isGeolocationAvailable, isGeolocationEnabled } =
useGeolocated({
positionOptions: {
enableHighAccuracy: false,
},
userDecisionTimeout: 5000,
});
getWeather(coords, API).then(data => {setWeather(data)})
return !isGeolocationAvailable ? (
<div>Your browser does not support Geolocation</div>
) : !isGeolocationEnabled ? (
<div>Geolocation is not enabled</div>
) : coords ? (
<div><p>Your weather at {coords.latitude} and {coords.longitude} {weather?.name}, is {Math.round(weather?.main?.temp)}</p>
<p>High/low: {Math.round(weather?.main?.temp_max)}/{Math.round(weather?.main?.temp_min)}</p></div>
) : (
<div>Getting the location data… </div>
);
};```

That happens because in JSX, all of the code gets executed during each re-render. So each time your component re-render, the getWeather(...) will be called once, which is kind of scary.
useEffect is the right tool to use for things like on-mount API call, I'm not sure what "'reading 'latitude' in the fetch call" really means, maybe you can try this:
useEffect(() => {
if (coords && isGeolocationAvailable && isGeolocationEnabled) {
getWeather(coords, APIkey).then(setWeather)
}
}, [coords, isGeolocationAvailable, isGeolocationEnabled])
Notes:
If APIkey is sensitive data, you probably shouldn't put it right in your component. Try using dotenv or something else to store those variables, depending on your development environment.
The code in useEffect will be executed every time when any of its dependencies changes. So if you only want it to run once, either carefully select what should be put into dependency list, or use a boolean flag to prevent it from calling more than one time.

Related

RTK Query calls not initiating before render

I have a couple of query calls that were working previously, but now are not firing off, while the many others I have throughout the app continue to work perfect. I've done some updating and adjustments elsewhere in the app, but none of which should have affected these calls. On the Redux dev tool, the calls never even show, as if they never happen. The only error I get is from the subsequent data being undefined.
UPDATE/EDIT
I've dialed in the issue, but am still confused. If I comment out the call and only display the status (isSuccess, isLoading, isError) the call goes out, completes, and returns success and I can verify the data in the devtools. However if I try to use the data, react is crashing before the data is returned.
Here's one of the calls:
import React from 'react';
import { useGetUsersQuery } from '../redux/apiSlice';
import { SupervisorTab } from './userviews/SupervisorTab';
export default function Teams() {
const { data } = useGetUsersQuery()
const teams = data.filter(e => e.role === "Supervisor")
return(
<div>
<h1>Teams</h1>
{teams && teams.map(t => <SupervisorTab supervisor={t} key={t._id} /> )}
</div>
)
}
and the corresponding endpoint on the apiSlice:
getUsers: builder.query({
query: () => '/userapi/users',
providesTags: ['User']
}),
I attempted to provide a useEffect hook to only interact with the data once the call is successful, but the same issue is occurring even within the hook.
const [teams, setTeams] = useState()
const { data, isSuccess, isLoading, isError, error } = useGetUsersQuery()
let content
if (isLoading) {
content = <h1>Loading...</h1>
} else if (isError) {
content = <h1>Error: {error}</h1>
} else if (isSuccess) {
content = <h1>Success</h1>
}
useEffect(()=>{
console.log(data)
//below are 2 scenarios that illustrate the issue, they're run at separate times...
setTeams(data)
//this will provide the correct data, set it to state, and the 2nd log below shows the same.
const teamInfo = data.filter(e => e.role === "Supervisor") //
setTeams(teamInfo)
//this call fails saying data is undefined and the initial console log states undefined
}, [isSuccess])
console.log(teams)
I've not had an issue with this before, typically I put in the query hook, it gets called and completed before the final render, without any UI crash for undefined values. Still, using useEffect, it should only interact with the data once it is available (isSuccess), yet it is crashing during the attempt to interact within useEffect.
I'm ok with React, but have not seen this behavior before. If anyone has a clue as to why or how to resolve, please let me know.

How to invalidate react-query whenever state is changed?

I'm trying to refetch my user data with react-query whenever a certain state is changed. But ofcourse I can't use a hook within a hook so i can't figure out how to set a dependency on this state.
Current code to fetch user is:
const {data: userData, error: userError, status: userStatus} = useQuery(['user', wallet], context => getUserByWallet(context.queryKey[1]));
This works fine. But I need this to be invalidated whenever the gobal state wallet is changed. Figured I could make something like
useEffect(
() => {
useQueryClient().invalidateQueries(
{ queryKey: ['user'] }
)
},
[wallet]
)
but this doesn't work because useQueryClient is a hook and can't be called within a callback.
Any thoughts on how to fix this?
General idea is wallet can change in the app at any time which can be connected to a different user. So whenever wallet state is changed this user needs to be fetched.
thanks
useQueryClient returns object, which you can use later.
For example:
const queryClient = useQueryClient()
useEffect(
() => {
queryClient.invalidateQueries(
{ queryKey: ['user'] }
)
},
[wallet]
)

Unexpected behaviour of useState (do not renew a constant after getting server data)

I have a very simple react code, which I use to track containers location on a territory. After a new container get's into the territory I have props.operationsList changed. So I send get response to server API when props.operationsList changes
useEffect(() => {
async function fetchContainerLocation() {
const response = await CoordinatesService.getContainersPosition()
console.log('response = ', response.data.features)
setContainersList(response.data.features)
console.log('containersList = ', containersList)
}
fetchContainerLocation()
}, [props.operationsList])
I need to update containersList const, that I use to rerender a map API where I should locate the containers. I define it like that:
const [containersList, setContainersList] = useState([])
I need to set containersList in accordance with that response fron server (response.data.features) to make my map rerender. What's strange,
console.log('response = ', response.data.features)
shows accurate and correct data from server, but the next
console.log('containersList = ', containersList)
is not equal with this response
Instad of getting the map rendered with the right data from server response, I have wrong data. So, I do now understand why such an straightforward approch do not work and how to fix it
State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately.
So, try to log your containersList outside useEffect and compare both logs. both should be same.
TIP: While using map method with your containerList use it like containerList?.map() so that page does not turn out to be blank.
const fetchContainerLocation = async () => {
const response = await CoordinatesService.getContainersPosition();
console.log("response = ", response.data.features);
setContainersList(response.data.features);
};
useEffect(() => {
fetchContainerLocation();
}, [props.operationsList]);
console.log(containerList);
return (
<>
{containerList?.map((container) => (
<p>something you want to render</p>
))}
</>
);
No idea why, but it worked when I changed response.data.features
to [...response.data.features]
Working code
useEffect(() => {
async function fetchContainerLocation() {
setContainersList([])
const response = await CoordinatesService.getContainersPosition()
setContainersList([...response.data.features])
}
fetchContainerLocation()
}, [props.operationsList])
If anybody could explain why, it would be useful

React useEffect with Stripe - get client secret asynchronously

I've been plopped into the middle of a react project with no prior knowledge so I'm sure this a duplicate.
I want to use the StripeElements, and the react library is easy enough. I'm just not sure how to get the client secret from the server.
This is what I'm trying:
const stripePromise = loadStripe('stripe_key');
const StripeForm = () => {
const [stripeData, setStripeData] = useState({});
const getClientSecret = async () => {
const { data } = await api.get(`payment/stripe/get-client-secret/`);
return data
}
useEffect(() => {
getClientSecret().then(data => setStripeData(data))
}, [])
if(stripeData.clientSecret) {
return (
<QuoteContainer title={"Pay With Credit Card"}>
<SectionCard>
{stripeData.clientSecret}
</SectionCard>
</QuoteContainer>
);
}
return (<b>Loading</b>)
}
The route payment/stripe/get-client-secret/ returns an array with a key 'clientSecret'. This function is working correctly.
I just can't get the <b>Loading</b> text to be replaced with the QuoteContainer component once the promise is resolved.
If you want to rendering Loading component. You have to set falsy value for stripedData state. Because {} value is truthy. So I think the code is not rendering Loading component. The loading component is not rendered because there is no place to set it as a false value anywhere.
what AJAX library are you using to make the API call? Usually you need to call a function on the response object in order to access the data. For instance, if you are using fetch(), you need to call json() then access the response data.

My react component never displays the information from the database

I have a small web app displays game information.
I am using React hooks so that the component is modern.
When this component loads, I want it to connect to the api via axios, and get the description of the game.
But when it loads, the value inside the <GameVault /> is always null.
When I look in the database, it is not null. If I hit the api directly, it does return the game description.
My console.log is hit twice for some reason. The first time it's null, the second time it has the needed value.
I am also not getting any errors, so I don't know why this isn't working.
Here is the code:
const Vault = ({ game }) => {
const [gameText, setGameText] = useState(null);
async function fetchGameText() {
const response = await axios.get(`/api/gamermag/${game.id}/gameDescriptionText`);
setGameText(response.data);
}
useEffect(() => {
fetchGameText();
}, []);
console.log("gameText: ", gameText);
const gamerValue = useMemo(() => {
return gameText ? gameText : "";
}, [gameText]);
return (
<GameVault value={gamerValue} />
)
}
export default Vault;
Is there a way to get this to work?
Thanks!
You need to wait for the data to load from the server. While the data is being fetched, gameText will be null and when it's done fetching, it stores the response. That is why your console.log hit twice. The first time is the component's first render, and the second time is when the gameText changes its state which caused a rerender.
You need to add logic to wait for the data.
if(!gameText){
return <div>loading...</div>
}

Resources