Skip to content

[BUG] queryOptions.select runs multiple times #6713

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

Open
jpenna opened this issue Mar 19, 2025 · 10 comments · May be fixed by #6763
Open

[BUG] queryOptions.select runs multiple times #6713

jpenna opened this issue Mar 19, 2025 · 10 comments · May be fixed by #6763
Labels
bug Something isn't working

Comments

@jpenna
Copy link

jpenna commented Mar 19, 2025

Describe the bug

I'm using GraphQL and I'm changing the data with the React Query's select middleware. It should run only once per request, but it is running multiple times.

Steps To Reproduce

  1. Use the useList hook
  2. Add queryOptions.select: (data) => { console.log('select'); return data }
  3. See "select" is logged multiple times

Replication CodeSandbox: https://codesandbox.io/p/sandbox/react-query-5-forked-hh85pv

Expected behavior

It should run only once. In React Query's documentation, they say to memoize the select function: https://tanstack.com/query/latest/docs/framework/react/guides/render-optimizations#memoization

useList is changing the function reference on every render.

Packages

  • "@refinedev/core": "^4.54.0",

Additional Context

Possible fix: pass the select function inside a useCallback to useList's useQuery

@jpenna jpenna added the bug Something isn't working label Mar 19, 2025
@alicanerdurmaz
Copy link
Member

Hello @jpenna, Thanks for the detailed explanation.

I believe your suggested fix will solve the problem, Do you want to work on this?

@jpenna
Copy link
Author

jpenna commented Mar 24, 2025

Hey @alicanerdurmaz , I was going to send it, but the contribution process seemed to require more time than I can afford at the moment...

@alicanerdurmaz
Copy link
Member

Hey @alicanerdurmaz , I was going to send it, but the contribution process seemed to require more time than I can afford at the moment...

We release a new version in the first week of every month. If you're not available, I can open a PR, or maybe someone else can do it. No problem, thank you!

@arndom
Copy link
Contributor

arndom commented Apr 6, 2025

I followed the suggestion of @jpenna, but it was still an issue. On logging the dependencies of the callback, the prefferedPagination was being updated

const querySelectCB = (rawData: GetListResponse<TQueryFnData>) => {
    let data = rawData;

    const { current, mode, pageSize } = prefferedPagination;

    if (mode === "client") {
      data = {
        ...data,
        data: data.data.slice((current - 1) * pageSize, current * pageSize),
        total: data.total,
      };
    }

    if (queryOptions?.select) {
      return queryOptions?.select?.(data);
    }

    return data as unknown as GetListResponse<TData>;
  }
  const querySelect = useCallback(querySelectCB, [prefferedPagination, queryOptions?.select]);

  console.log([prefferedPagination, queryOptions?.select])

// select is then assigned querySelect in https://github.com/refinedev/refine/blob/ce72ded1a746cd66699832dc3d8b28d37c1c511c/packages/core/src/hooks/data/useList.ts#L273 

prefferedPagination is calculated by the below, and even when memorized, due to pagination changing weirdly (from what I can see) on initial call, memorization solves nothing

  const prefferedPagination = handlePaginationParams({
    pagination,
    configPagination: config?.pagination,
    hasPagination: prefferedHasPagination,
  });

In my test within a useList in the app-crm-minimal example, there was no pagination passed so I'm stuck:

// inserted in: https://github.com/refinedev/refine/blob/ce72ded1a746cd66699832dc3d8b28d37c1c511c/examples/app-crm-minimal/src/routes/tasks/list/index.tsx#L30

    meta: {
      gqlQuery: TASK_STAGES_QUERY,
    },
    queryOptions: {
      select: React.useCallback((data) => {
        console.log("select", Math.random());
        return data;
      }, []),
    },

@jpenna
Copy link
Author

jpenna commented Apr 9, 2025

Hey, @arndom , thanks for taking this up!

Did you try to memoize the result from handlePaginationParams? It seems to me this is causing the object to change on every render and then the callback is re-created again. I think this whole function can be memoized with the params. Something like the following:

export const handlePaginationParams = ({
  hasPagination,
  pagination,
  configPagination,
}: HandlePaginationParamsProps = {}): Required<Pagination> => {
return useMemo(() => {
  const hasPaginationString = hasPagination === false ? "off" : "server";
  const mode = pagination?.mode ?? hasPaginationString;

  const current =
    pickNotDeprecated(pagination?.current, configPagination?.current) ?? 1;

  const pageSize =
    pickNotDeprecated(pagination?.pageSize, configPagination?.pageSize) ?? 10;

  return {
    current,
    pageSize,
    mode,
  };
}, [hasPagination, pagination.mode, pagination?.pageSize, configPagination?.pageSize, configPagination?.current])
};

Btw, we should add to the documentation that the queryOptions.select must be memorized in the client component.

@arndom
Copy link
Contributor

arndom commented Apr 10, 2025

I did attempt to memoize the already exported implementation of handlePaginationParams while using its three args as dependencies. It yields the same results as what you've shown above.

The only time I got the intended result was when memorizing queryOptions.select in useList. So, I'm leaning towards this as the solution, ⬇️:

Btw, we should add to the documentation that the queryOptions.select must be memorized in the client component.

@jpenna
Copy link
Author

jpenna commented Apr 14, 2025

Yeah, we will have to memoize queryOptions.select, otherwise it will always be a new component. React Query also says the select function should be memoized https://tanstack.com/query/latest/docs/framework/react/guides/render-optimizations#memoization

@arndom
Copy link
Contributor

arndom commented Apr 15, 2025 via email

@jpenna
Copy link
Author

jpenna commented Apr 21, 2025

@arndom Are you saying we have to memoize both select and the "pagination-function" passed to useList? Could you send a PR with the working solution you got? I think it will be easier to discuss on top of concrete code.

@arndom arndom linked a pull request Apr 26, 2025 that will close this issue
5 tasks
@arndom
Copy link
Contributor

arndom commented Apr 26, 2025

Sorry about the late reply @jpenna. I've created an unready PR for this. In the implementation of useList, I memoized the select from useQuery and also the pagination-function: preferredPagination.

But all that did not work because the pagination dependency of preferredPagination changes, causing it to be re-created. And I couldn't find a way to keep track of it.

What worked was memoising the queryOptions.select where the useList is called, as seen from the app-crm-minimal example that is part of the PR.

#6763

@alicanerdurmaz alicanerdurmaz linked a pull request Apr 28, 2025 that will close this issue
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants