RTK Query calls not initiating before render - reactjs

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.

Related

Rendering order for React apps - why does a "Too many re-renders" error occur?

I'm new to React and modifying an existing starter app.
I'm implementing a check to see if the user is logged in, based on the existence of a token in localStorage. I modified my App.js file from this:
function App() {
let [ isLoggedIn, setLoggedIn ] = useState(false);
return (
<><Index isLoggedIn={isLoggedIn} setLoggedIn={setLoggedIn} /></>
);
to this:
function App() {
let [ isLoggedIn, setLoggedIn ] = useState(false);
const storageToken = localStorage.getItem('token');
if (storageToken) {
setLoggedIn(true);
} else {
setLoggedIn(false);
}
return [same as above]
This change results in a Uncaught Error: Too many re-renders. error. Putting the check in Index.js works fine.
Looking into this some more (eg from this other question or this blog post), I understand that it's because you can't modify a state in the same function where you declare it with useState because that state change causes the overall function to run again. But my question is: why? I suspect this is background info about how React works / the order in which React calls components, so any background reading on that topic would be appreciated!
I think you should read this for understading how Reactjs actually works in these cases
App is a functional component, which has its code run on every render. Changing state triggers a new render. By calling setLoggedIn() in the main body of the function, you’re creating an infinite loop.
The solution is to only read from localStorage when the component mounts/unmounts. This can be done with an effect.
useEffect(() => {
// code to run on component mount
const storageToken = localStorage.getItem('token');
if (storageToken) {
setLoggedIn(true);
} else {
setLoggedIn(false);
}
}, []);

Why is useEffect not working here? Do I need to change the condition?

I am trying to run this code, but useEffect is not running a single time.
export default function DetailPage() {
const [post, setPost] = useState({});
const postId = useParams().postId;
console.log(postId);
useEffect(() => {
console.log("useEffect called");
const fetchPost = async () => {
const res = await axios.get(`/posts/${postId}`);
setPost(res.data);
console.log(post);
console.log("useEffect runs");
};
fetchPost();
console.log("useEffect runs 2 ");
}, [postId]);
}
Here, I am getting postId in the console, but not "useEffect run".
I have used similar code (except I am using another variable there instead of postId) in another file, and it's working there.
Please help me with this code.
Be careful, when you use setState the value of your state is changed asynchronously. So to you, it seems that the state doesn't change, but in reality, it will change.
This because when you try to print the value of a state just after the setState, its value still wasn't updated.
When you want to debug how a state changes with console log, create a separate hook that log every change.
Add something like this:
useEffect(()=>{
console.log("Post state changed!")
console.log(post);
},[post])
For what concerns the issue that your last log (console.log("useEffect runs");) is not running the only possible issues are:
postId changes are not triggering useEffect (possible reason: you are using a ref inside your useParams to store values). To check it just put a console.log before running the Axios request:
const fetchPost = async () => {
//Does this ever display your postId? If it does go to point 2.
console.log(`Trying to fetch post ${postId}`);
//Furthermore if postId can be undefined I'll wrap this request with an if
const res = await axios.get(`/posts/${postId}`);
setPost(res.data);
console.log("useEffect runs");
};
There is an uncaught error in your network request.
First tip: always put a try-catch block when awaiting an Axios response.
This will avoid missing uncaught errors.
try {
const res = await axios.get(`/posts/${postId}`);
setPost(res.data);
} catch(e) {
//Here log your error. Remember to avoid showing the log in production
}
When working with Axios I also always suggest checking the network tab of your browser to understand what is going on with your request.
I link you a post where I explain how to do it: https://stackoverflow.com/a/66232992/14106548

React useState hook not updating with axios call

I am aware this is a common question as I have spent the last two hours going through every answer and trying to get my state to update but nothing is working.
I am fetching text from a cms however on first load the state is undefined and my app crashes. However if I comment the line out, load the page and uncomment the line the correct values are displayed.
Here is some of the code I have tried.
The data i am hoping to get
[
{id:1},
{id:2},
{id:3},
{id:4},
]
import react, {useEffect, useState} from 'react'
import axios from 'axios'
const [carouselTitle, setCarouselTitle] = useState([])
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
await axios('api').then(
response => {
console.log(response.data)
setCarouselTitle(response.data)
console.log(carouselTitle)
})
};
return(
<h1>{carouselTitle[1].id}</h1>
)
console logging the data works fine but when i console log the state it does not work.
2ND METHOD I TRIED
useEffect(() => {
const fetchData = async () => {
const res = await axios('api');
const carouselTitleAlt = await res.data;
setCarouselTitle({ carouselTitleAlt });
console.log(carouselTitleAlt);
console.log(carouselTitle);
};
fetchData();
}, []);
Again console logging the const inside the useEffect displays the correct information but logging the state does not work.
Appreciate your responses or better ways of displaying the data.
setState is asynchronous : https://reactjs.org/docs/faq-state.html#why-doesnt-react-update-thisstate-synchronously
It means that you cannot expect to console.log the new state value the line after you called setCarouselTitle.
To log the new value, you could use another useEffect, with carouselTitle in the dependencies array, where you console.log(carouselTitle) :
useEffect(() => {
console.log(carouselTitle);
}, [carouselTitle]);
That said, your component should behave correctly, it will be refreshed when the state is updated.
In the JSX you should check that carouselTitle is not undefined (meaning that the request failed or is still pending) :
{carouselTitle && <H1>{carouselTitle[0].id}}
https://reactjs.org/docs/conditional-rendering.html#gatsby-focus-wrapper
First of all, if you pass an empty array for initial data to useState, you can't get any item in that array in here:
return(
<h1>{carouselTitle[1].id}</h1>
)
Because component returns first item of an array that has nothing. I prefer to you do it like this:
return(
<h1>{carouselTitle.length > 0 && carouselTitle[0].id}</h1>
)
And also based on this and official documentation, setState (and also setSomthing() using useState()) is asynchronous.
So state data doesn't show immediately after setting that.
You should trigger useEffect for run fetch function
useEffect(()=>{fetchData();},[carouselTitle])

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>
}

How to derive "loading" from useSWR between fetches without revalidation?

I was asked a question regarding SWRs "loading" state:
How do you create a loading state from SWR between different URL fetches?
Their docs make it appear straight forward:
const { data, error } = useSWR(`/api/user/${id}`, fetcher)
const isLoading = !error && !data;
However, this logic seems to fail after the first render of the hook/component. On the first render data is undefined. Then loads and data becomes a value to consume in the UI.
Let's say I change the id via the UI and want to show loading indicator. Because data is no longer undefined, the same logic fails.
There is an additional item returned isValidating. So I updated my logic:
const isLoading = (!data && !error) || isValidating
However, this could be true when:
there's a request or revalidation loading.
So in theory something else causes my component to rerender. This could inadvertently cause a "revalidation" and trigger loading state gets shown. This could break the UI temporarily, by accident.
So how do you derive "loading" between URL changes without revalidation? I am trying to replicate how graphQL Apollo Client returns const { loading, error, data } = useQuery(GET_DOGS);
Let's say I change the id via the UI and want to show loading indicator. Because data is no longer undefined, the same logic fails.
data will be undefined again when you change the key (id), if it doesn't have a cache value.
Remember that in SWR { data } = useSWR(key) is mentally equivalent to v = getCache(k), where fetcher (validation) just write to the cache and trigger a re-render.
data is default to undefined, and isValidating means if there's an ongoing request.
Alternatively, you can derive loading through the use of middleware. Here's what I use...
loadingMiddleware.ts
import { useState } from 'react'
import { Middleware } from 'swr'
const loadingMiddleware: Middleware = (useSWRNext) => (key, fetcher, config) => {
const [loading, setLoading] = useState(false)
const extendedFetcher = (...args) => {
setLoading(true)
try {
return fetcher(...args)
} finally {
setLoading(false)
}
}
const swr = useSWRNext(key, extendedFetcher, config)
return { ...swr, loading }
}
export default loadingMiddleware
App.tsx
import { SWRConfig } from 'swr'
import loadingMiddleware from './loadingMiddleware'
const App: FC = () => {
...
return (
<SWRConfig value={{ use: [loadingMiddleware] }}>
...
</SWRConfig>
)
}
export default App
Update (12/13/22)
swr#v2 is out and provides isLoading and isValidating properties in the return value of useSWR.
Here's the difference between the two according to the swr docs.
isValidating becomes true whenever there is an ongoing request whether the data is loaded or not.
isLoading becomes true when there is an ongoing request and data is not loaded yet.

Resources