TanStack / query

🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.
https://tanstack.com/query
MIT License
40.54k stars 2.73k forks source link

Unable to use react-query when hooks are created in separate library #3595

Closed SoftMemes closed 1 year ago

SoftMemes commented 2 years ago

Describe the bug

I have a monorepo with yarn 3 workspaces consisting of the following (as well as other components):

The only direct dependencies on react-query are through contracts, my UI project does not directly use react-query.

I'm now running into a few issues to do with module resolution, and am not sure how much of this relates to yarn, workspaces, react-query, or next magic.

React-query cannot find QueryProvider

If I make react-query a peer dependency of contracts, then initialize react-query in my next project (set a query provider, etc), then the hooks that use react-query under the hood from contracts do not find the QueryClient. It appears to "see" its own version of react-query even though this is a peer dependency of contracts and I provide it as a direct dependency in ui.

I am able to work around this by having contracts instead directly depend on react-query, and re-export the symbols from react-query (export * from 'react-query'). This appears to give the hooks incontracts` access to the same react-query and the context / query provider is found.

... or at least it was. Without any meaningful changes (I'm working on pinning down the exact change), I am no longer able to do it this way, as react-query now fails to find react. Inside the compiled code of react-query, I am getting React as an undefined symbol and this line blows up var defaultContext = /*#__PURE__*/React.createContext(undefined); iin react/QueryClientProvider.js

I appreciate that this isn't much to go on, but in general, what is the recommended method for providing custom hooks based on react-query in a library in terms of dependencies and configuration of the query client?

Your minimal, reproducible example

N/A

Steps to reproduce

  1. Build a library that leverages react-query in custom hooks with react-query as a peer dependency
  2. Use the library from above in a different project, providing react-query as a direct dependency
  3. Configure react-query with a QueryClientProvider in the dom where the hooks from 1 are being used

Expected behavior

React-query can "see" the context and query provider wired up in the react DOM, even when using peer dependencies and the react-query library is provided as a dependency form the parent project.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

react-query version

3.39.0

TypeScript version

4.6.4

Additional context

No response

mauris commented 2 years ago

it's hard to tell what went wrong without knowing how your monorepo setup is like. I use nx for my monorepo setup, with the following packages:

I don't face the issue you've described on what I have:

might be worthwhile for you to share the problem in a minimal reproducible setup

TkDodo commented 2 years ago

I appreciate that this isn't much to go on, but in general, what is the recommended method for providing custom hooks based on react-query in a library in terms of dependencies and configuration of the query client?

if you want a shared scope, meaning that the library uses the same context / client as the application that includes it, I would do it like you've described (I think):

the useQuery calls in the lib should just pick up that client. If that's not the case, you likely have either two versions of react-query or two versions of react around.

if you want an isolated scope, I would take a look at the custom context feature for v4.

TkDodo commented 2 years ago

Any updates here? Is this still an issue?

nctay commented 1 year ago

Having same issue with turborepo monorepo setup, extracting queries to different package causes it.

TkDodo commented 1 year ago

@nctay can you link to an example repo that has the issue please?

petercpwong commented 1 year ago

Having the same issue as well with a NextJS pnpm monorepo setup, even after upgrading to react-query v4. Had to pass in defaultContext to every single hook to get it to work. I'm also using next-transpile-modules so that could be what's causing the issue.

It works flawlessly with a vite pnpm monorepo setup though.

stamahto commented 1 year ago

I have same issue too

TkDodo commented 1 year ago

It's honestly not very helpful stating that you have the issue too without providing a minimal reproduction that we can look into. So far, no one has created a reproduction, so there is nothing I can look into. I'm gonna close this now, feel free to reopen once you can share a reproduction.

mverissimo commented 1 year ago

I faced this issue too, you need to check if have the equal version of react and react-query and both packages.

stamahto commented 1 year ago

I faced this issue too, you need to check if have the equal version of react and react-query and both packages.

I do, still not working. I´m gonna have to learn publishing packages into npmjs and create reproduction case. It will take me some time, but I hope it will be worthy.

SoftMemes commented 1 year ago

Hi,

Not a repro, and I'm travelling so will be a brief message from the phone, but I know at least what caused this now. Using yarn 3 with a pnpm style repo (this may apply to other setups but this is where I've seen it), a dependency will only be shared if it matches exactly. This means that the same version has to be resolved which is easy enough to guarantee with version ranges but also critically, the same dependencies need to have been resolved.

This interacts with in particular dev dependencies. If the project A dependends on library B, both which use a third party package C (for example react query), but A and B have direct dependencies in such a way that the dev dependencies of C are fulfilled by different versions, then A and B will see different instances of C.

The way to check this is to compare where the symlink for C is pointing in node modules for A vs B, the compare the node modules in turn for the instance of C used by A vs C used by B, and tweak dependencies of each until they match perfectly.

marissync commented 1 year ago

Having the same issue as well with a NextJS pnpm monorepo setup, even after upgrading to react-query v4. Had to pass in defaultContext to every single hook to get it to work. I'm also using next-transpile-modules so that could be what's causing the issue.

It works flawlessly with a vite pnpm monorepo setup though.

How did you do that? Facing same issue in next.js

petercpwong commented 1 year ago

@marissync The useQuery hook accepts a context option. Just pass in the defaultContext exported by react query.

import { defaultContext, useQuery } from '@tanstack/react-query';

useQuery({ context: defaultContext });
marissync commented 1 year ago

@marissync The useQuery hook accepts a context option. Just pass in the defaultContext exported by react query.

import { defaultContext, useQuery } from '@tanstack/react-query';

useQuery({ context: defaultContext });

Thank you, @petercpwong!!

wiredmatt commented 1 year ago

Instead of asking for a failing reproduction wouldn't it be easier to provide a working one? I've tried what was suggested above but it's still failing in the production bundle. I'm using exact versions of react and react-query.

arlyon commented 1 year ago

I can corroborate the issue with next-transpile-modules.

Edit: @nctay @marissync @petercpwong I was able to resolve this issue by adding a resolve alias in my next.config.js webpack settings:

  // react-query causes issues with next-transpile-modules
  // so we need to override the resolutions to prevent duplicates
  webpack: (config, options) => {
    if (options.isServer) {
      config.externals = ["react-query", ...config.externals];
    }

    // this resolves to react-query/lib/index.js, so jump up two dirs
    const reactQuery = path.resolve(require.resolve("react-query"), "../../")

    config.resolve.alias['react-query'] = reactQuery
    config.resolve.alias['react-query/devtools'] = path.resolve(reactQuery, "devtools")

    return config;
  }
luan-hte commented 1 year ago

I had the same issue when using react-query with @tanstack/react-query-devtools. I removed react-query and installed @tanstack/react-query and the issue was gone. Basically use both packages from @tanstack.

szmarci commented 1 year ago

@system32uwu @SoftMemes @TkDodo I had the same issue. In this case for some reason pnpm installed two versions of react query (taken from the lockfile):

/@tanstack/react-query/4.3.4:
/@tanstack/react-query/4.3.4_sfoxds7t5ydpegc3knd667wn6m:

The problem went away when I changed peerDependencies in all of my libraries that included react-query as a peer dependecies to include react as well, so changing from this:

"peerDependencies": {
   "@tanstack/react-query": "^4.3.4"  
}

To this:

"peerDependencies": {
   "@tanstack/react-query": "^4.3.4",
   "react": "^17.0.2",
   "react-dom": "^17.0.2"
}

And after a pnpm install, only one version is installed:

/@tanstack/react-query/4.3.4_sfoxds7t5ydpegc3knd667wn6m:

szmarci commented 1 year ago

If someone wants to investigate it further, I created a minimal repository: https://github.com/szmarci/pnpm Master branch is the working one, fail branch is... well, the failing one.

This is the only difference in the source files (fail branch -> master branch): image

And after install these are the lockfile differences: image

image

jrea commented 1 year ago

in a monorepo, using a lib within the repo. I'm using yarn workspaces.

I added the lib as a peer dep, and added a webpack resolver in next.config.js like this:

  if (options.isServer) {
      config.externals = ['@tanstack/react-query', ...config.externals];
    }

    const reactQuery = path.resolve(require.resolve('@tanstack/react-query'));

    config.resolve.alias['@tanstack/react-query'] = reactQuery;
ecyrbe commented 1 year ago

@TkDodo do you think the issue i linked above can come from esm support ? only one instance of @tanstack/react-query is installed, zodios is declaring dependencies as peer dependencies for both react-query and react. i checked installed packages, no duplicate.
seems related to webpack resolution, webpack seems to create two instance duplicate instances of react-query in final build. The bug was not happening on react-query v3, that's why i suspect esm modules.

@jrea workaround works like a charm, but it's ugly to tell zodios users to resolve to such hacks. i know ts-rest lib also has the same issue : https://github.com/ts-rest/ts-rest/issues/66

ecyrbe commented 1 year ago

@TkDodo do you want me to open a new issue for this ?

ecyrbe commented 1 year ago

@TkDodo i confirm the bug appeared in @tanstack/react-query v4.3.0 and everything was fine on v4.3.2 I'll create an issue since i now have reproductible test case

rametta commented 1 year ago

@ecyrbe Do you have a link to the issue you created? I am also experiencing this issue

ecyrbe commented 1 year ago

@rametta here : https://github.com/TanStack/query/issues/4346

But i closed it since i found that the issue is not in tanstack query but in webpack+next . The only way to make it go away is for your library to export esm + cjs packages

rametta commented 1 year ago

Interesting, I also made a reproduction repo https://github.com/rametta/react-query-v4-bug-repro but it's not using Next, it's using Vite. I will try to export both esm and cjs in my package, thank you

rametta commented 1 year ago

Yup confirmed it works now with ESM, I need to add these options to my typescript tsc compilation command --moduleResolution node --target es2015 --esModuleInterop and defined both in my package.json, cjs in the main field and esm in the module field

rametta commented 1 year ago

@ecyrbe Actually I'm still having the issue with NextJS even though I'm building the ESM now. Vite is fixed, but NextJS is still throwing the same error. Any idea why? Anything special I need to define in NextJS?

ecyrbe commented 1 year ago

@rametta Your package.json need to declare exports. Take a look at how i do it here: https://github.com/ecyrbe/zodios/blob/main/package.json

rametta commented 1 year ago

@ecyrbe That doesn't seem to help unfortunately. I got something semi working by specify "type": "module" in my package.json and importing directly from the esm dir in my code, but I wish it was more automatic

ecyrbe commented 1 year ago

There is an ugly workaround for next if all other things don't work (idea from @jrea ) : https://github.com/ecyrbe/zodios-express/blob/a237332b38cf0d8edb49edafb66c2d659a1d35f5/examples/next/next.config.js

rametta commented 1 year ago

Actually your previous comment did work, I just realized I needed to delete the Next cache (.next dir) before rebuilding because I was still getting the old package.json 🤦 What a rollercoaster 😄 Thanks again

Darkhorse-Fraternity commented 1 year ago

@rametta It is just useful in localhost. It fails when running in git action or docker. See here: https://github.com/Darkhorse-Fraternity/monad-stack/actions/runs/3538172759/jobs/5938774870#step:7:175

rametta commented 1 year ago

The solution that worked for me also worked in GitHub actions with a nextjs prod build consuming the package

adairrr commented 1 year ago

I was able to reproduce the issue, which seems to be something with yarn v3 dependency resolution here: https://github.com/adairrr/missing-query-provider-repro

The interesting part of the issue is that running yarn why @tanstack/react-query elicits only one version:

yarn why @tanstack/react-query
└─ dapp@workspace:packages/dappery            
   └─ @tanstack/react-query@npm:4.19.0 [ca7b3] (via npm:^4.19.0 [ca7b3])
emurano commented 1 year ago

https://github.com/vitejs/vite/issues/6780#issuecomment-1059677499

Make sure your library has all peer dependencies listed in your rollup config in your vite config file: build.rollupOptions.external

johnayoung commented 1 year ago

Just want to add here that I also had this problem with my tsconfig.json. Removing additional "lib" declarations solved it for me:

Previous { "compilerOptions": { "target": "ES5", "lib": ["ES2017", "ES7", "ES6", "dom"] }, }

New { "compilerOptions": { "target": "ES5" }, }

nctay commented 1 year ago

in a monorepo, using a lib within the repo. I'm using yarn workspaces.

I added the lib as a peer dep, and added a webpack resolver in next.config.js like this:

  if (options.isServer) {
      config.externals = ['@tanstack/react-query', ...config.externals];
    }

    const reactQuery = path.resolve(require.resolve('@tanstack/react-query'));

    config.resolve.alias['@tanstack/react-query'] = reactQuery;

Alias definitely works for this case.

React-query V4 update:

webpack: (config, options) => {
      if (options.isServer) {
        config.externals = ['@tanstack/react-query', ...config.externals]
      }
      config.resolve.alias['@tanstack/react-query'] = path.resolve(
        require.resolve('@tanstack/react-query'),
        '../../../'
      )
      return config
    }
leo-petrucci commented 1 year ago

I'm really confused. If I add @tanstack/react-query to my peer dependencies the only thing that happens is that I can't build my package anymore:

(typescript) Error: semantic error TS2307: Cannot find module '@tanstack/react-query' or its corresponding type declarations.
  "peerDependencies": {
    "@tanstack/react-query": "^4.22.0",
    "react": "18.x",
    "react-dom": "18.x"
  },

I've even tried passing context to my hooks but nothing, it's not working. I don't want people to have to install react-query in their own project just to use my package.

Is there really no way to fix this without custom webpack configs or weird workarounds?

emurano commented 1 year ago

I don't want people to have to install react-query in their own project just to use my package.

Then you need to provide react-query as a dependency instead of a peer dependency. Peer dependencies are a declaration that the module using your module must have the dependency installed in order to use your module.

leo-petrucci commented 1 year ago

I don't want people to have to install react-query in their own project just to use my package.

Then you need to provide react-query as a dependency instead of a peer dependency. Peer dependencies are a declaration that the module using your module must have the dependency installed in order to use your module.

I'm aware of that, but if I do that my package crashes when it's used because of the issue mentioned in the OP.

rwieruch commented 1 year ago

Just for the purpose of documenting it somewhere: We got the Error: No QueryClient set, use QueryClientProvider to set one in a project too -- which happened because we had two react-query installations.

The setup with the problem:

- app/
--- package.json <- react-query installation
--- shared-library/
------ docs/
--------- package.json <- react-query installation
------ packages/
--------- a/
------------ package.json
--------- b/
------------ package.json
--------- c/
------------ package.json

The architecture used a git submodule to nest a shared library into various applications. Because of this issue here, we restructured the whole architecture to a monorepo structure to have the react-query installations not nested but side by side instead -- which causes a shared yarn.lock file in the end and a more robust dependency resolution.

The new setup without the problem:

- shared-library/ <- monorepo
--- app/
------ package.json <- react-query installation
--- docs/
------ package.json <- react-query installation
--- packages/
------ a/
--------- package.json
------ b/
--------- package.json
------ c/
--------- package.json
minisaw commented 1 year ago

in a setup with several modules, make sure that all of them have their typescript compiler options (tsconfig.json) aligned in the following property:

"compilerOptions": { "module": "esnext" }

in case there is a mixture between "esnext" and "commonjs" module values, you will end up having several copies of the react-query library, leading to "Error: No QueryClient set, use QueryClientProvider to set one".

bombillazo commented 1 year ago

React-query V4 update:

webpack: (config, options) => {
      if (options.isServer) {
        config.externals = ['@tanstack/react-query', ...config.externals]
      }
      config.resolve.alias['@tanstack/react-query'] = path.resolve(
        require.resolve('@tanstack/react-query'),
        '../../../'
      )
      return config
    }

This worked for us! But since our next.config.js is at the root, we didn't have to add the path traversing.

bertyhell commented 1 year ago

Mine worked like this, so you don't have to guess how many levels up you have to go if the nextjs is the root:

webpack: (config, options) => {
    // Fix issues with react-query:
    if (options.isServer) {
        config.externals = ['@tanstack/react-query', ...config.externals];
    }
    config.resolve.alias['@tanstack/react-query'] = path.resolve(
        './node_modules/@tanstack/react-query'
    );

    return config;
}
donalnofrixion commented 10 months ago

For anyone else having this problem when using Vite.

You need to add @tanstack/react-query to the vite.config.ts rollupOptions

rollupOptions: { external: ['react', 'react-dom', 'axios', '@tanstack/react-query'],

tqhoughton commented 4 months ago

I appreciate that this isn't much to go on, but in general, what is the recommended method for providing custom hooks based on react-query in a library in terms of dependencies and configuration of the query client?

if you want a shared scope, meaning that the library uses the same context / client as the application that includes it, I would do it like you've described (I think):

  • have the lib define react-query as peerDependency
  • have the app create the queryClient and the QueryClientProvider.

the useQuery calls in the lib should just pick up that client. If that's not the case, you likely have either two versions of react-query or two versions of react around.

if you want an isolated scope, I would take a look at the custom context feature for v4.

We have this exact setup and are still running into this No QueryClient set, use QueryClientProvider to set one issue with react-query v4. Figured that as long as the app that imports the component is wrapped in a QueryClientProvider it would be fine as you said but somehow it doesn't seem to be finding the right version of react-query from the lib component even though it has react-query and react as a peer dependency. Confirmed that there is only 1 version of @tanstack/react-query in the yarn.lock file in the app repo.

Is converting our lib component to export as an ESM module really the only option we have to fix this?

adrianzielonka commented 1 month ago

I appreciate that this isn't much to go on, but in general, what is the recommended method for providing custom hooks based on react-query in a library in terms of dependencies and configuration of the query client?

if you want a shared scope, meaning that the library uses the same context / client as the application that includes it, I would do it like you've described (I think):

  • have the lib define react-query as peerDependency
  • have the app create the queryClient and the QueryClientProvider.

the useQuery calls in the lib should just pick up that client. If that's not the case, you likely have either two versions of react-query or two versions of react around. if you want an isolated scope, I would take a look at the custom context feature for v4.

We have this exact setup and are still running into this No QueryClient set, use QueryClientProvider to set one issue with react-query v4. Figured that as long as the app that imports the component is wrapped in a QueryClientProvider it would be fine as you said but somehow it doesn't seem to be finding the right version of react-query from the lib component even though it has react-query and react as a peer dependency. Confirmed that there is only 1 version of @tanstack/react-query in the yarn.lock file in the app repo.

Is converting our lib component to export as an ESM module really the only option we have to fix this?

See @donalnofrixion's answer above, it should solve your issue.

austinlangdon commented 3 weeks ago

Both our next.js (webpack) app and react (vite) app faced this issue within our monorepo.

Here were the solutions for both

Vite: add a resolve alias to vite.config.ts

resolve: {
  alias: {
    '@tanstack/react-query': path.resolve(__dirname, './node_modules/@tanstack/react-query')
  }
}

Next.js: add a resolve alias to next.config.ts

webpack: (config, { isServer }) => {
    if (isServer) {
      config.externals = ['@tanstack/react-query', ...config.externals];
    }
    config.resolve.alias['@tanstack/react-query'] = path.resolve(
      './node_modules/@tanstack/react-query'
    );

    return config;
  },