-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
useQueries hook causing infinite re-renders when using result as a dependency in useEffect #5137
Comments
same as useQuery, where the top level object is also a new object in every render cycle, the useQueries array is "new", but the The code snippets don't make much sense to me, as they don't show any real life use-case. What would you do in a |
@TkDodo for function Component() {
const queries = useQueries();
const someExpensiveCalculation = useMemo(() => {
queries.map(/* ... */);
}, [queries, /* [...] */); // memoization is broken because of `queries`
} The only way I could fix it for that case for now was to pick the attributes I needed (data, isError, isLoading, etc) and to force a shallow comparison to make the reference stable. |
I'll think about this problem. I guess what would be good is to have a
whatever you return from I'm not sure if we could do this and have it work well with TypeScript, so I'm looping in @artysidorenko |
Thanks! The way I solved it for now was by doing this: function shallowCompareObjects<T extends {}>(a: T, b: T) {
return Object.keys(a).every((key) => a[key] === b[key]);
}
function shallowCompareCollections<T extends {}>(a: T[], b: T[]) {
return a.length === b.length && a.every((item, index) => shallowCompareObjects(item, b[index]));
}
function useMemoizedCollecionPick<T extends {}, P extends keyof T>(array: T[], pickValue: readonly P[]): Pick<T, P>[] {
// eslint-disable-next-line react-hooks/exhaustive-deps
const stablePickValue = useMemo(() => pickValue, [JSON.stringify(pickValue)]);
const value = useMemo(() => array.map((item) => pick(item, stablePickValue)), [array, stablePickValue]);
const prevValue = useRef(value);
return useMemo(() => {
if (shallowCompareCollections(value, prevValue.current)) {
return prevValue.current;
}
prevValue.current = value;
return value;
}, [value]);
}
const result = useQueries();
const pickedResult = useMemoizedCollecionPick(result, ['data', 'error', 'isLoading']); which gets me a little bit closer to I'm guessing |
Heya, yeah I just had a play around locally as well, I think something like the below will work well in getting the types for this*. export function useQueries<T extends any[], CombinedResult = QueriesResults<T>>({
queries,
context,
}: {
queries: readonly [...QueriesOptions<T>]
context?: UseQueryOptions['context']
combine?: (result: QueriesResults<T>) => CombinedResult
}): CombinedResult { *the |
I started working on this, but it's not trivial. I wanted to add it on observer level to have this feature available in core, but we determine suspense and error boundary features on framework level. If So I think it needs to happen on framework level, after suspense / errorBoundary handling. But there, we'd have a harder time memoizing the previous value to also guarantee referential stability. It might be that I'll add Oh also, if it comes, it will come in v5 because we have so many changes already that I don't want to deal with the conflicts 😅 . |
Just commenting here because I am also running into this issue. |
shipped in v5 alpha.34 |
For those who can't update yet, my current fix for memoizing from the queries is to increment a counter on success and then memoize from that counter: // Doesn't work
const queries = useQueries(/* */);
const expensiveValue = useMemo(() => {/* */}, [...queries]);
// Does work
const successfulQueriesCounter = useRef(0);
const queries = useQueries([
{ onSuccess: () => successfulQueriesCounter.current = successfulQueriesCounter.current + 1 }
]);
const expensiveValue = useMemo(() => {/* */}, [successfulQueriesCounter.current]); |
Other Similar Issues: #2991
Infinite re-renders happen when trying to update local state with other than reference type of queries response in useEffect where we added queries result in dependencies array.
And also when any query response is not consistent(Meaning for first call of "posts" search gives [posts] and when no posts, return response as {message: "No data found."}), You will infinite re-renders.
Here are some examples to get better idea:
Your minimal, reproducible example
Please look at code snippet provided
Steps to reproduce
Hope you got better Idea from above snippet.
Expected behavior
useQueries should memoize the response and should not force the consumed component to update same response when adding as dependency in useEffect.
How often does this bug happen?
Every time
Platform
OS: MacOS
Browser:Chrome
TanStack Query version
v3.39.3
The text was updated successfully, but these errors were encountered: