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>
}
Related
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.
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.
I am fetching data from a backend api.
const Home = () => {
const [posts,setPosts]=useState([]);
useEffect(()=>{
const fetchPosts= async ()=>{
const res = await axios.get("/posts")
setPosts(res.data);
}
fetchPosts();
},[])
console.log(posts)
However in doing this, the initial state of posts which is [] is also getting printed.
Why is this happening and how can I avoid this and only get the data?
that is the reason why you see loading in real-world App because communication between server and client takes time so we show loading or show error if API gets error the same logic goes to component or element whatever your are trying to show as well
if(posts.length > 0) {
console.log(posts)
} else {
console.log("Loading...")
}
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.
I have multiple getServerSideProps in my project and I have a header which displays pages and I have to wait for a page to be opened once I click upon it since I need data to be fetched. Once they are fetched the page will be open.
One approach I used to show user a loading state is to use routeChangeStart BUT I stumbled upon one problem and so I would like not to use this case.
If I go on a page and the data is fetching I want to show user a spinner or some indicator and once the data is fetched I want to stop the indicator/spinner.
As you probably figured out, getServerSideProps runs on the server and is blocking. The fetch request needs to complete before the HTML is sent to the user (i.e., the page is changed). So if you want to show a loading indicator, you need to move that fetch request to the client.
For instance, if you probably have a page with this basic structure:
export default function Page({ data }) {
return <div>{data.name}</div>
}
export async function getServerSideProps() {
const response = await fetch('https://example.com/api')
const data = await response.json()
return {
props: { data },
}
}
const fetcher = url => fetch(url).then(res => res.json());
export default function Page() {
const { data } = useSWR('https://example.com/api', fetcher)
if (!data) return <LoadingSpinner />
return <div>{data.name}</div>
}
Or if you don't need SWR and can use a simple fetch request:
export default function Page() {
const [data, setData] = useState()
useEffect(() => {
fetch('https://example.com/api')
.then(async(response) => {
const json = await response.json()
setData(json)
})
})
if (!data) return <LoadingSpinner />
return <div>{data.name}</div>
}
P.S. If the initial fetch request in getServerSideProps used sensitive information (e.g., API secret credentials), then go ahead and setup a Next.js API route to handle the sensitive part and then fetch the new route.
I just used routeChangeStart.
I didn't want to use it since router.push('/map') didn't work in pages/index.tsx file but I solved this issue by creating a new component putting router.push in useeffect and rendering a loader.
routeChangeStart was in _app.js and because of this in index.js router.push() didn't work - I tested it
routeChangeStart - how it works?
When we click on a page the data is being fetched on the server and the page will only be displayed to us once the data is fetched. So we can make the next thing, we can just intercept the route change.
When we click on a link(we wait for data to fetch) we set loading state in routeChangeStart to true and if we moved to another page(it means we fetched the data) we invoke routeChangeComplete which runs once we moved to the route we wanted to, and here we set loading state to false. And after this I just pass the loading state using React Context