T
TanStack3y ago
optimistic-gold

React Query Infinite Re-Rerender

Hello, I'm noticing a weird behavior with my code. I'm pretty certain I'm doing something wrong, I'm just not sure what.
type UseServerStoreProps<Data> = {
name: string;
itemId: string;
getFn: () => Promise<Data>;
demoData: Data;
};

export function useServerStore<Data>(props: UseServerStoreProps<Data>) {
const { name, itemId, getFn, demoData } = props;
const demo = useDemo();
const user = useUser();

const userId = useMemo(() => {
if (demo.isDemo) return 'demo';
if (!user.data.user) throw new Error('useServerStore called without user');
return user.data.user.id;
}, [demo.isDemo, user.data.user]);

const queryKey = ['store', name, userId, itemId, 'server'];

const query = useQuery({
queryKey,
async queryFn() {
if (demo.isDemo) return demoData;
return getFn();
},
suspense: true,
meta: storeMeta,
networkMode: 'offlineFirst',
staleTime: isDev ? ms('1 minute') : ms('1 day'),
cacheTime: ms('30 days'),
// enabled: false,
});

// ...

if (!query.data) throw new Error('suspense not triggered');

return {
data: query.data,
query,
};
}
type UseServerStoreProps<Data> = {
name: string;
itemId: string;
getFn: () => Promise<Data>;
demoData: Data;
};

export function useServerStore<Data>(props: UseServerStoreProps<Data>) {
const { name, itemId, getFn, demoData } = props;
const demo = useDemo();
const user = useUser();

const userId = useMemo(() => {
if (demo.isDemo) return 'demo';
if (!user.data.user) throw new Error('useServerStore called without user');
return user.data.user.id;
}, [demo.isDemo, user.data.user]);

const queryKey = ['store', name, userId, itemId, 'server'];

const query = useQuery({
queryKey,
async queryFn() {
if (demo.isDemo) return demoData;
return getFn();
},
suspense: true,
meta: storeMeta,
networkMode: 'offlineFirst',
staleTime: isDev ? ms('1 minute') : ms('1 day'),
cacheTime: ms('30 days'),
// enabled: false,
});

// ...

if (!query.data) throw new Error('suspense not triggered');

return {
data: query.data,
query,
};
}
The gist of what is going on is that I have a hook to fetch some data from the server and cache it locally. That works great! Now I'm trying to add a demo mode so that a user can play around with some mock data before signing into the product. For some reason, the second I add if (demo.isDemo) return demoData;, I start to get infinite re-renders. Removing the line immediately fixes it, adding it back breaks it again. I am using things like the persist plugins, and actually noticed that it's constantly writing to the localStorage (the timestamp increases). However inspecting the items, the dataUpdateCount is a super low number. Any obvious ideas as to what is going on?
10 Replies
optimistic-gold
optimistic-goldOP3y ago
One hunch I had was that this return is not async. I had my throttle time set to 0 but even with 100 it still has the same issue Interestingly, if I open and close the page a few times, it eventually "settles" and shows me the underlying page I console.logged the JSON that was being fetched and compared them and the only difference is the timestamp If I do
if (demo.isDemo) {
const test = await new Promise<Data>((resolve) => {
setTimeout(() => resolve(demoData), 1000);
});
return test;
}
if (demo.isDemo) {
const test = await new Promise<Data>((resolve) => {
setTimeout(() => resolve(demoData), 1000);
});
return test;
}
it works without issue
absent-sapphire
absent-sapphire3y ago
Hi. As a first thought you should include the isDemo bool in your query key since used in the queryfn. Having it or not changes the data set so it should be reflected in the query key.
optimistic-gold
optimistic-goldOP3y ago
Hmm, in my code, both the itemId and userId are different if its demo so the queryKeys will never accidentally be the same in other words i already handle the case of isDemo = true and isDemo = false having different query keys
absent-sapphire
absent-sapphire3y ago
Yup indeed my bad
optimistic-gold
optimistic-goldOP3y ago
No worries, it was a good point Bump if anyone else could take a look
conscious-sapphire
conscious-sapphire3y ago
If you console.log queryKey is it stable or is it constantly changing during the infinite rerenders? Also if you add a console.log are the beginning of queryFn, is it infinitely called too? Also what is demoData compared to the usual return value of getFn()? Could the rerenders simply happen somewhere down the line because demoData is returned? Is demoData a stable reference and is the data used in a useMemo/useEffect/useCallback somewhere?
optimistic-gold
optimistic-goldOP3y ago
1. Always stable [1] 2. Yes [2] 3. demoData is the exact type of the return value of the network call. I dont think this is a case because when I add the setTimeout it works fine 4. Not a stable reference, but I did const test = useMemo(() => demoData, [demoData]); and returned test and it still infinitely re-rendered
No description
No description
optimistic-gold
optimistic-goldOP3y ago
Tried looking at devtools (this is in a chrome extension but I built it and opened it in a browser page). The profiler says "Did not render" on everything. I enabled "highlight components on rerender" and oddly it doesnt do anything (nothing is re-rendering), but there are infinite logs Also tried putting the queryKey array into memo, didn't help Interestingly, while the infinite console.logs are happening, I can still interact with the page, it's just the suspense is always shown. (I'm using react-suspense) Okay setting the setTimeout to 1ms or 0ms works
conscious-sapphire
conscious-sapphire3y ago
What about Promise.resolve(demoData)?
optimistic-gold
optimistic-goldOP3y ago
Nope, that leaves it infinitely loading again

Did you find this page helpful?