reduxjs / redux-toolkit

The official, opinionated, batteries-included toolset for efficient Redux development
https://redux-toolkit.js.org
MIT License
10.72k stars 1.17k forks source link

Issues using RTK Query outside function components #3994

Open markerikson opened 10 months ago

markerikson commented 10 months ago

@markerikson , this might be a bit of a hijack... sorry. I have a use case that seems pretty reasonable but I just can't get started. I have been happily using RTK for few years and now want to start using RTK Query. Within function components with hooks (as long as I add a few @ts-ignores due to this), it seems to be working ok.

Unfortunately, I need to be able to also use the store outside of function components (in inferno class components via inferno-redux -> I have some super-high-performance needs and plain react is just too slow...) and I just can't get started. I can't get any of the examples here to run and there don't seem to be any instructions on the expected way to run them. The supported way to install now appears to be with vite (which I have also been using for years) but most/all of the examples still use create-react-app, and I'm getting various different errors trying to install. Because there I couldn't find instructions, I don't know whether I'm just holding it wrong.

It is probably temporary but codesandbox also seems a bit foobared. I tried to download the class-based components example but it refused to download (something about Chrome now refusing sandbox downloads?) and there are also errors when trying to create a github repo.

So I am now a couple of hours in and still can't get an official example running. Is there something else I should be doing? Thanks!

Originally posted by @AntonOfTheWoods in https://github.com/reduxjs/redux-toolkit/issues/3910#issuecomment-1859440783

markerikson commented 10 months ago

@antonofthewoods moved this to a new issue.

@AntonOfTheWoods yeah, the Redux core examples folder is years out of date, and frankly we should just delete all of them.

I'm afraid the rest of your question isn't really clear enough to provide an answer. I don't know what errors you're running into with installs, and installing Redux Toolkit + React-Redux works the same way with CRA and Vite (just npm i @reduxjs/toolkit react-redux, or whatever package manager you're using).

Actually, re-reading your comment, now I'm extra confused :) Are you talking about the RTK Query-specific examples folder here? That's also probably somewhat outdated - I think some of them are still referring to the original alpha package.

Can you give more specifics about what you need to do and what problems you're running into?

AntonOfTheWoods commented 10 months ago

@markerikson thanks for your quick reply (and amazing reactivity on everything redux!). I couldn't get any of the sandboxes to work on my machine (after manually going through and copy pasting all the contents of the individual files... because codesandbox wouldn't give it to me as a repo or archive) -> I am getting a Error: error:0308010C:digital envelope routines::unsupported error an none of the interwebs fixen worked for me in the 15-20 minutes or so I spent trying to get it to work. The examples all seem to require --legacy-peer-deps (at least with npm), and even with that I get constant:

anton@zub:~/dev/tmp/redux-toolkit/examples/query/react/basic$ npm run start

> @examples-query-react/basic@1.0.0 start
> react-scripts start

Failed to compile.

Cannot find module 'acorn'
Require stack:
- /home/anton/dev/tmp/redux-toolkit/node_modules/acorn-import-assertions/lib/index.js
- /home/anton/dev/tmp/redux-toolkit/node_modules/webpack/lib/javascript/JavascriptParser.js
- /home/anton/dev/tmp/redux-toolkit/node_modules/webpack/lib/javascript/JavascriptModulesPlugin.js
- /home/anton/dev/tmp/redux-toolkit/node_modules/webpack/lib/WebpackOptionsApply.js
- /home/anton/dev/tmp/redux-toolkit/node_modules/webpack/lib/webpack.js
- /home/anton/dev/tmp/redux-toolkit/node_modules/webpack/lib/index.js
- /home/anton/dev/tmp/redux-toolkit/node_modules/react-scripts/scripts/start.js

npm ERR! Lifecycle script `start` failed with error:
npm ERR! Error: command failed
npm ERR!   in workspace: @examples-query-react/basic@1.0.0
npm ERR!   at location: /home/anton/dev/tmp/redux-toolkit/examples/query/react/basic

Trying to install it in node_modules or globally does nothing. I guess there is a small chance it's related to me using asdf node but I've never had an issue with vite (or anything else to do with node, python or elixir related to asdf).

In terms of what I need to do - basically I am migrating a part of my app that has been using a local db (wasm sqlite) as the source of truth with a second level of RTK caching to putting the source of truth from a REST api and so RTKQuery. Previously I just passed the store via the inferno context (extremely similar to plain react class context stuff if I understand correctly) and had the cache available. I don't really need or want subscriptions at this level, there are so many objects it would probably kill performance, explode memory or both. There are other things that can force a refresh if needed. I still want RTK because in other parts of the app I am using normal function components and I want/need all the normal behaviour.

If I do

const { data: knownWords } = await this.context.store.dispatch(knownWordsApi.endpoints.getKnownWords.initiate(""));

It looks like I'm able to get the data though I'm picking this is not going to scale much above my simple tests. If I try and refer to the state like I did before (via this.context.store.getState().knownWords) then I get some weird kind of service object rather than the object/dict I had before.

There may be some way to get it to do what I need but I haven't been able to work it out yet. If I try and do the

const result = api.endpoints.getKnownWords.select("")(state)
const { data, isSuccess, isError, error } = result

Then I just get lots of undefined and both isSuccess and isError are false. For some reason it also forces me to put an arg for the select even though it doesn't need one (and the example doesn't have one).

Typically in this sort of case I like to start from an official example that works and then just push it towards what I want to do, then adapt for my actual projects. Here I have been unable to use that method! Thanks again for all your help.

markerikson commented 10 months ago

@AntonOfTheWoods I do think it might help to take a step back and separate out the different topics and concerns you've listed :)

The example project build errors are annoying, but ultimately irrelevant to the larger goal of trying to get this working in your app. One thing to try there would be either updating the examples to "react-scripts": "^5" and see if that helps the install steps, or creating a new Vite project with our Redux template, and then copy over the example source code into that project.

Per the usage questions:

dispatch(someEndpoint.initiate()) and store.getState() are two very different steps. dispatch(initiate()) is dispatching a standard RTK async thunk to make the request and fill in the cache entry. store.getState() is getting the current overall store root state.

There shouldn't be any kind of a "service object" in the RTK Query section of the store state. It would be a plain JS object containing the cache data for the API slice, like {queries: {}, mutations: {}}.

I'm not sure what you mean by "this isn't going to scale for my app". Can you clarify that? Per https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks , that is how you'd use the plain RTK Query API without making use of React hooks.

Yes, api.endpoints.someEndpoint.select() requires some argument as the cache key. If you supply no argument, the cache key is undefined. Whatever cache key you supply has to exactly match what you passed into the initiate() thunk, because they both use the same logic to find the relevant cache entry.

Finally, note that the cache entries have a status field in them, but some of the derived values that get returned from the React hooks do not exist in the cache entry itself, so you may be trying to read fields that don't exist in the cache entry.

AntonOfTheWoods commented 10 months ago

The example project build errors are annoying, but ultimately irrelevant to the larger goal of trying to get this working in your app. One thing to try there would be either updating the examples to "react-scripts": "^5" and see if that helps the install steps, or creating a new Vite project with our Redux template, and then copy over the example source code into that project.

Yeah, that's always possible, but we are now a major version up, and I don't see anywhere in the docs "you can safely assume our code will always be valid, no matter how many major versions of redux we iterate through". As I don't know how things are supposed to work, blindly assuming copy/pasting old code will work exactly the same way as new releases to try and learn seems a little risky...

Per the usage questions:

dispatch(someEndpoint.initiate()) and store.getState() are two very different steps. dispatch(initiate()) is dispatching a standard RTK async thunk to make the request and fill in the cache entry. store.getState() is getting the current overall store root state.

Yeah, this is where things get a little murky for me... I get RTK and RTK Query are just opinionated layers on top of Redux but not so much what the public APIs are supposed to look like. Your comments below are starting to make things clearer though!

There shouldn't be any kind of a "service object" in the RTK Query section of the store state. It would be a plain JS object containing the cache data for the API slice, like {queries: {}, mutations: {}}.

Ok, I was calling that a "service object". With my non RTK Query code that is the particular state dictionary I'm interested in directly. I was erroneously assuming that's what I should expect from RTK Query also -> it appears I should be expecting to interact with the "low level" stuff, rather than what I actually care about (actual values).

I'm not sure what you mean by "this isn't going to scale for my app". Can you clarify that? Per https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks , that is how you'd use the plain RTK Query API without making use of React hooks.

So 30k+ subscriptions per page is fine (on an iphone with their stupid memory limits)? I can have 30k+ words on a page (each word has a multi-level html fragment), and each word would need a subscription. I should obviously test this out first rather than ask though!

Yes, api.endpoints.someEndpoint.select() requires some argument as the cache key. If you supply no argument, the cache key is undefined. Whatever cache key you supply has to exactly match what you passed into the initiate() thunk, because they both use the same logic to find the relevant cache entry.

For this particular usecase I'm mainly interested in getting a single result set of lots of items (a set of words with, say, 0 to 10k entries) that I devalidate every now and then for various reasons.

I may be abusing the system a bit with how I'm using though...

markerikson commented 10 months ago

@AntonOfTheWoods part of my point is that you're listing several different kinds of problems, some of which are very irrelevant to your ultimate goal of getting RTK Query working in your app. That feels like a distraction overall.

So 30k+ subscriptions per page is fine

No, this seems like it would be extremely bad for perf.

Even in a desktop app, having thousands of subscribed components on screen at once is bad. (I talked to Slack devs earlier this year, and they had 20K connected components on screen, and were having to go to incredibly outlandish lengths to keep the app performant.)

What is your app doing that you feel like you would need 30K subscriptions to the store?

I think, based on your description, that what you really want is one component asking for that data and passing the values down as props to all the nested children, but I'd need more details on the actual app itself.

but tbh I'm kind of lost at this point in the discussion :)

My main recommendation would be to read through https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks and see what questions you have after that.

rwilliams3088 commented 2 months ago

Going through this right now and 100% agree that the docs, whilst a good starting point, are simply incomplete and fail to tell you how to fully perform a query without react hooks. I think they assume familiarity with redux and performing such operations with redux without react hooks more generally.

markerikson commented 2 months ago

@rwilliams3088 what specifically would you expect to see in that docs page?

are simply incomplete and fail to tell you how to fully perform a query without react hooks

Not sure I understand this comment. https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks shows const promise = dispatch(api.endpoints.getPosts.initiate()), which is the key piece of info you need to know to trigger a fetch.

I think they assume familiarity with redux

Yes, that's very much the case. We have to make assumptions so we don't end up repeating the same explanations all across the docs, so there's no reason to explain Redux basics like "dispatching" and store.getState() in an RTKQ usage guide page.

rwilliams3088 commented 2 months ago

@markerikson I would expect a full example. Which means not simply issuing the request, but how to receive the results. At minimum, detail the whole process and provide links to the relevant doc so that one has everything needed to put together a solution.

In my case, RTK Query is the only thing that I use Redux for - so the doc in question is rather frustrating to me as someone not immersed in the redux ecosystem.

markerikson commented 2 months ago

@rwilliams3088 : we can try to add some more examples, but at the same time: it sounds like you're expecting this docs page to explain core Redux concepts, and that is not the point or goal of that particular docs page. Each page has a specific purpose.

If you want to understand those core concepts, our primary tutorials are in the core Redux docs site:

In this particular case, dispatch(api.endpoints.getPosts.initiate()) returns a promise for the data you've asked for, so const data = await dispatch(api.endpoints.getPosts.initiate()) should be the starting piece that you need. (The snippet shows const promise = dispatch() to clarify that the promise has an extra refetch method attached, which is not something you'd normally see attached to a promise.)

rwilliams3088 commented 2 months ago

If awaiting the promise was sufficient to get the results, then the doc would indeed be sufficient. However, what it actually returns is an object with { status: "pending", ... } - so awaiting the promise does nothing to get the results.

Additionally, the example code doesn't work with typescript. I have had to cast things as any for now which is really undesirable.

phryneas commented 2 months ago

However, what it actually returns is an object with { status: "pending", ... } - so awaiting the promise does nothing to get the results.

That should not happen and I'd consider that a bug. Can you create a reproduction?


Generally, "outside of React" is a bit of a problem: it works different in Vanilla than it does in Angular than it does in Vue than int does in Svelte than it does in Solid. Can you make it work in all of them? Sure. Do we know all of those? Nope :/

And that's ignoring that "Vanilla JS" is just a description for 200 more completely different approaches, including other frameworks like Lit.

rwilliams3088 commented 2 months ago

That's a fair point. Which is why I'd like the example to at least talk through the whole process with a reference link or two so I can compose my own solution with my tech stack.

It would be troublesome to try to take everything I'm doing right now and turn that into a small reproduction (I'm implementing a feature flags server with Action Cable + AWS + Redis to automatically broadcast changes down to React clients and have them auto-update accordingly; lots of parts). I'm implementing a REST service with RTK Query in parallel to the Action Cable LIVE service, and I wanted to speed-up the page initialization by using the REST service to lookup a particular feature-flag value.

That said, I will try to find time to create a minimal reproduction as I am able. Just not sure when I'll have some down time to work on it offhand.

psychedelicious commented 1 month ago

It looks like the initiate docs are missing a key bit of info - you must unwrap the query action creator result to get the response (or error). For example:

const result = dispatch(api.endpoints.listItems.initiate());
const items = await result.unwrap();

The createAsyncThunk docs describe this API well.

phryneas commented 1 month ago

You do not have to unwrap, it's just a convenience thing. The promise will always resolve to either a success or a rejection action, so you can just as well look at the .payload or .error properties of that returned object.

EskiMojo14 commented 1 month ago

note that initiate actually wraps the promise, so instead of getting the action you get an object that either looks like { data } or { error }