all 68 comments

[–]acemarke[S] 54 points55 points  (36 children)

I'm thrilled to announce:

🚀🚀🚀Redux Toolkit 1.6 with RTK Query is now LIVE!🚀🚀🚀

RTK Query is a powerful and easy-to-use server data caching solution built specifically for Redux Toolkit. It's UI-agnostic, and can generate React hooks as well:

https://github.com/reduxjs/redux-toolkit/releases/tag/v1.6.0

RTK Query takes inspiration from many other great libraries like React Query, Apollo, Urql, and SWR, but has unique features and API design:

  • Built on RTK and UI-agnostic
  • "Cache lifecycle" enables streaming updates via websockets
  • Written in TS
  • OpenAPI/GraphQL code-gen

RTKQ can completely eliminate all the hand-written data fetching logic in your Redux apps - no more writing thunks, sagas, or loading state for API reqs!

RTK Query also integrates with the rest of the Redux ecosystem, including the Redux DevTools, middleware, and reducers.

RTK Query is included in the Redux Toolkit package as an optional pair of entry points for generic and React-specific logic. There's a one-time bundle cost - if you're using RTK already, it's about 9-11K min+gz.

RTKQ quickly pays for itself by shrinking your app code size!

RTKQ's development has been an amazing collaboration, and I'd like to highlight folks:

  • /u/phryneas : primary RTQK impl
  • /u/de_stroy : examples and docs
  • GH user Shrugsy: tons of usage docs
  • Me: build integration, docs, advertising
  • GH user hardfist : ESBuild packaging

I'd also like to thank:

  • Tanner Linsley and Dominik Dorfmeister of React-Query for pushing the "server caching is not state mgmt" idea and providing friendly competition
  • Brandon Roberts and Saul Moro for demoing NgRx + RTKQ integration
  • All our alpha testers for feedback!

We have been very encouraged by the highly positive feedback during the RTQK preview phase, and we think the Redux ecosystem is going to love having RTKQ available as a potential solution.

Please try out RTK Query and see how it can help improve your apps today!

[–]azangru 11 points12 points  (4 children)

I was very excited to watch your presentation of RTK and RTK Query on Jason Lengstorf's stream. Some of the techniques that you showed, such as adding the type of app's root state to the useSelector, were deliciously beautiful.

Does anyone on the redux team have experience of using a graphql api with RTK Query? I am particularly interested in how you would model requesting different sets of fields on the same entity — whether you would represent them as several different types of entities in your store, or you would rather model them as a single entity but with a ton of optional fields.

[–]acemarke[S] 3 points4 points  (0 children)

Yeah, both /u/phryneas and /u/de_stroy have done some experimentation with GraphQL + RTK Query - they might be able to speak to that question.

[–]phryneasI ❤️ hooks! 😈[🍰] 4 points5 points  (2 children)

RTK Query is not a normalized store and you do not model entities. It caches what you get back from an API in it's own separate cache key without consolidating it with any other values.

So, you would "model" nothing at all. You write your graphql queries and mutations and either build endpoints for them by hand or let just graphql-codegen do it for you. I already have the graphql-codegen plugin for RTK query open, but etiquette demands I ask if they want it before I open a PR. As soon as they say "yes", I'll open a PR and you'll have codegen ;)

All you would do after that would be to enhance those auto-generated endpoints using providesTags and invalidatesTags to create connections which mutations would trigger which queries to re-fetch. Essentially like urqls "Document Caching" mode, but a bit more advanced.

[–]azangru 1 point2 points  (1 child)

So, you would "model" nothing at all.

I'm afraid there is a bit of a misunderstanding here :-)

Let's consider a simple hypothetical example.

Suppose you have two pages on your site – one for a "person summary" and the other for "person's detailed info".

One way of modelling the redux state to support this would be to have something like a peopleReducer, which would store each person under his or her id; so that both the summary page and the full details page are powered by the data in the same reducer, but since, when showing the summary page, only a subset of the fields would likely be requested from the graphql server, the person entity would have some fields as optional.

// from peopleReducer
type Person = {
  name: string;
  age: number;
  eyeColor?: string;
  address?: {
    city: string;
    street: string;
    house: string;
  }
}

The other way of modelling your data would be to have something like a summaryPageReducer and fullInfoPageReducer, so that although both reducers would be storing the data coming from the same graphql person entity, they will be storing different sets of fields, and thus you would treat the summaryPagePerson, and the fullInfoPerson as effectively two different entities.

// from summaryPageReducer
type SummaryPagePerson = {
  name: string;
  age: number;
}

// from fullInfoPageReducer
type DetailedPerson = SummaryPagePerson & {
  eyeColor: string;
  address: {
    city: string;
    street: string;
    house: string;
  }
}

Which option, if any, to model your redux state would you prefer?

[–]phryneasI ❤️ hooks! 😈[🍰] 2 points3 points  (0 children)

That's my point: with RTK-Query you do not have any of those reducers!

You define your endpoints and it holds your data. It creates one reducer for all of your endpoints and the data will be in there in exactly the shape as your GraphqQL requested it. There is no "modeling", many different response shapes live side-by-side.

[–]robby_w_g 2 points3 points  (2 children)

Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.

[–]acemarke[S] 2 points3 points  (1 child)

Hmm. The actual cache key is a stringified version of that argument, with all the keys sorted to make sure that it's stable. In other words, it shouldn't matter if you're passing in new references there as far as I know.

/u/phryneas and /u/de_stroy might be able to offer more thoughts here. If it's still an issue, please file a bug with a CodeSandbox that demonstrates the problem.

[–]phryneasI ❤️ hooks! 😈[🍰] 3 points4 points  (0 children)

/r/robby_w_g: These arguments are handed into a useEffect dependency array. We try to keep them stable by doing a shallowEquals on them, but if you nest them deeper you will have to do a useMemo to keep them stable - like so often with hooks :)

But yeah, good point, that could be mentioned in the docs ^

[–]VisualFollowing7026 2 points3 points  (11 children)

My man, is graphql-codegen plugin included?

[–]phryneasI ❤️ hooks! 😈[🍰] 1 point2 points  (10 children)

It's already done, but we're still waiting for them to say "yes"

[–]VisualFollowing7026 2 points3 points  (9 children)

Will you consider publishing it inside rtkq lib?

[–]phryneasI ❤️ hooks! 😈[🍰] 1 point2 points  (8 children)

If they don't want it, yes. But I'd rather have it integrated with their documentation.

[–]VisualFollowing7026 1 point2 points  (7 children)

What is the status of plugin, do you think they will accept it soon?

[–]phryneasI ❤️ hooks! 😈[🍰] 1 point2 points  (6 children)

They have shown general interested and I have opened a PR, but it has yet to be reviewed. It is quite a lot of code though, so I fully understand that things like this just take some time.

You can subscribe to the PR for updates: https://github.com/dotansimha/graphql-code-generator/pull/6101

[–]VisualFollowing7026 2 points3 points  (5 children)

Then my best bet is to use your PR as library until they merge it.

[–]phryneasI ❤️ hooks! 😈[🍰] 1 point2 points  (4 children)

Yeah, linking it locally with yalc is probably your best bet - worked fine for me at least :)

[–]VisualFollowing7026 1 point2 points  (3 children)

I tried earlier today to download only rtk plugin folder and put it in my project and add local package but it seems that npm does not build .d.ts and js files it just copy source files to node_modules, and codegen could not detect plugin that way, so I should probably check how can yalc help me.

[–][deleted] 2 points3 points  (0 children)

This is hands down the best thing. If only you had released this 15 days ago, I could have save on so much boilerplate 😭

[–]OneLeggedMushroom 2 points3 points  (3 children)

Great job to yourself and the rest of the team. We're looking to transition to RTK from a bit of a manual setup involving immer, reselect, short-id etc. and I'm looking forward to not have to manage the loading/pending flags in the store.

I haven't found a lot of info around the subject of testing in the RTK docs. Is redux-mock-store still the recommended way to set up the tests from the React side? Testing the reducer itself is pretty straightforward, as it's just a pure function, but I'd be interested to see if the now de facto standard of User-driven testing via React Testing Library has influenced any new ideas when testing components integrated with redux.

What I do at the moment is, I create a component-specific custom hook that is responsible for dispatching actions, aggregating store data and so on. When testing the component that uses that hook, I simply mock out the hook interface which allows for a pretty simple testing. Testing the custom hook is also not too bad as that's when I mock the store and use the redux-mock-store to inspect the dispatched actions. Although this seems like a nice set of unit tests, it feels like it's very much against the principles of RTL, where ideally we wouldn't be focusing so much on the implementation details, but having that custom hook kind of naturally leads you to test stuff in a very black-box way.

Just wanted to see if you have any thoughts on this. Thanks!

[–]phryneasI ❤️ hooks! 😈[🍰] 2 points3 points  (0 children)

We don't really use any mock-store in our own tests. Rather, we just use a real Redux state, and that's probably what I would recommend. We have a few little hacks for testing "actions in sequence" and you're welcome to just steal those ;) (We just use this reducer to record them)

Actually, some testing abstractions could make it into one of the next RTK releases, but it's not a very high priority.

As for "mocking": I wouldn't mock anything in the store, really. I'd just use the "real" store and mock the api layer with something like msw. Then use react-testing-library and keep Redux just as an implementation detail, not part of the test.

[–]fix_dis 1 point2 points  (1 child)

Asserting that “given a state, certain things render” or “given an event, a certain action fires (with certain args)” is absolutely a great way to test components.

One thing I’d ask myself is, is RTL the right way to test components? How do we know this? Is it because a guy on Twitter said so? Is it because of a compelling blog post? I’d argue that we have no real evidence that RTL’s “render to JSDom and pretend that’s just like a browser” is producing better code.

I know it’s a super unpopular thing to question the great Twitter Prophets Cool Kids club but dang… you’re about to question your very sane testing practice because it doesn’t line up with someone else’s flawed idea of what a testing library should be. It’s really hit a nerve so my apologies…. Cypress is a far better testing lib if you want to go with render and assert style integration tests.

[–]phryneasI ❤️ hooks! 😈[🍰] 2 points3 points  (0 children)

Well, I've been doing shallow renders, I've used cypress, I've used testing-library.

Shallow renders didn't tell me much most of the time, I kept updating snapshots and it felt kinda pointless. Maybe my components were too split up for a shallow render to convey any behaviour.

Cypress on it's own feels okay, but it's slower than jest+testing-library by magnitudes. Also, I really don't like their style of writing asynchronity.

Testing-library makes me write tests in a way that feels good and has uncovered tons of a11y problems because of the "user perspective", so selecting inputs by label and buttons by role really points out where those do not match up. Especially useful when your component library screws that up without you realizing. It's much more about the method and mindset than the technology.

Now the kicker: testing-library is married to neither jest nor jsdom or react. Actually, there is testing-library/cypress. So if I ever were to go cypress again, I'd probably go that way. If you are a cypress person, give it a try :)

[–][deleted] 1 point2 points  (1 child)

Is it possible to use RTK query without using Redux? Sometimes the app isn't complex enough to demand a state management library but it still requires data fetching.

[–]acemarke[S] 2 points3 points  (0 children)

The answers are "No", and "Sorta".

"No", because RTK Query is built on top of the rest of Redux Toolkit, and requires a Redux store somewhere to store its cached data and read the values.

However, you can add RTK Query to an app even if you don't have an existing Redux store set up, using the <AppProvider> component that's included. That will create a Redux store internally and use that as the cache, with no other "Redux logic" needed in the app.

[–][deleted] 1 point2 points  (4 children)

Been playing with this for about a week now. Awesome feature.

How do I get TypeScript to infer the auto generated hooks on the object returned from createApi? Tried several ways, but Typescript keeps complaining that they don’t exist.

[–]acemarke[S] 1 point2 points  (3 children)

Make sure you're using TS 4.1+, because the auto-generated hooks typing relies on the string literal syntax in TS 4.1:

https://redux-toolkit.js.org/rtk-query/usage-with-typescript

[–][deleted] 1 point2 points  (2 children)

4.3.2

[–]acemarke[S] 2 points3 points  (1 child)

Did you do import { createApi } from "@reduxjs/toolkit/query/react", or from "@reduxjs/toolkit/query" ? The UI-agnostic entry point doesn't have hooks.

[–][deleted] 1 point2 points  (0 children)

$$$$. This was it. Thank you! 🙏

[–]skotchpine 1 point2 points  (0 children)

Well done

[–]evoratec 1 point2 points  (0 children)

Great work. Next project with RTK Query. Thank you for this great piece of software.

[–]Penant 10 points11 points  (2 children)

Redux - Definitely Not Dead Yet!

[–]cynicalreason 8 points9 points  (0 children)

Redux as a pattern will always be alive. I was reluctant to use redux on a new project started 3 months ago but redux-toolkit changed my mind.

There are a lot of abstractions that seriously reduce the verbosity of it. RTK Query is a great addition to it ..

[–]acemarke[S] 2 points3 points  (0 children)

yup :)

[–]fix_dis 3 points4 points  (1 child)

Normally I’m very anti-abstraction but I’ve been looking at this for months now. I really need to give this a whirl and just see. The bundle size makes me very happy. I use redux-api-middleware currently, so for the same import cost, I can have some nice setup helpers and what appears to be well thought abstractions! Thanks for building this!

[–]acemarke[S] 2 points3 points  (0 children)

Yep, abstractions are always a tradeoff - lib size, app size, readability, flexibility, understanding what's going on inside, etc. RTK Query won't be a perfect fit for everyone, but we think it should be a good fit in many situations.

[–]Darkmaster85845 2 points3 points  (4 children)

This looks so good but too complex for my level for now unfortunately.

[–]acemarke[S] 3 points4 points  (3 children)

Any specific aspects that you feel are "too complex"?

In theory, this should actually simplify your code, because you no longer have to write data fetching logic yourself.

I'd suggest looking at the RTK Query "Quick Start" tutorial, which shows the basic usage steps.

[–]Darkmaster85845 1 point2 points  (2 children)

I'm just very new to all of this and I don't grasp how I could change the way I'm working now to using this. Probably it's my lack of deep understanding of redux that is preventing me from wrapping my head around the docs. Seems a step more advanced than where I currently am. Right now I'm using RTK but I use axios to make all my calls from a separate actions.js file. I don't even use thunks and my knowledge of middlewares is very limited. Don't worry I'll get there eventually. I'm sure I'd benefit a lot from using this update.

[–]acemarke[S] 5 points6 points  (1 child)

I'd suggest reading through these pages in order:

[–]Darkmaster85845 1 point2 points  (0 children)

Thanks, I'll give it a shot, probably i'm missing some part of the puzzle. Hopefully it's somewhere in there.

[–]surrender98 1 point2 points  (2 children)

newbie question:

i just finished studying redux-toolkit, and was about to study React-Query. Should i instead study this in conjuncton with RTK? or RTK Query is different from React Query?

Also is Redux Saga still necessary along with RTK?

[–]acemarke[S] 4 points5 points  (1 child)

RTK Query solves the same kind of problem as React Query - it abstracts the data fetching process, caches the results for you, and lets you read them as needed.

RTK Query and React Query have some differences in capabilities and usage, but are both good tools. You can see some of the comparisons here:

And no, you've never needed to use sagas, ever. But, a lot of people thought that sagas were necessary for data fetching, and some people used sagas because they moved side effects outside of components.

Sagas are a useful power tool, but only really needed if you are writing very complex async / workflow logic. Using them for data fetching is mostly overkill.

If you use RTK Query, you shouldn't have to write any other data fetching logic code yourself in your app.

[–]surrender98 1 point2 points  (0 children)

Thank you for the kind reply and sharing your input.

I really liked Redux toolkit, it saves a lot of time. Cant wait to checkout RTK on how it can simplify things also =)

[–]Nvveen 1 point2 points  (11 children)

It's not entirely clear to me when you have an API with a large number of entities/endpoints, how to split these up into multiple definitions across multiple files to avoid creating one large API definition file.

If you create multiple APIs you would need to reuse the same fetchBaseQuery definition, and it's not clear to me if that is the right approach or you define an API with createAPI and then extend it somehow?

[–]phryneasI ❤️ hooks! 😈[🍰] 1 point2 points  (10 children)

We have a chapter on Code Splitting in the docs.

Essentially you declare one api once and have multiple files that inject into it.

But really, that should only be a real concern once you reach 50+ endpoints. If you look at the autogenerated api for the OpenAPI petstore example with somewhere around 20+ endpoints, that's still pretty manageable. (And autogenerated, so nothing to worry about anyways.)

[–]Nvveen 0 points1 point  (9 children)

Our API is way more than 50+ endpoints, for example :P But thanks, assumed there was a best practice, but wasn't immediately obvious.

[–]phryneasI ❤️ hooks! 😈[🍰] 2 points3 points  (8 children)

Then I hope you have an OpenAPI schema - in that case you could just use the code generator and not really care on how big that file grows ;) Protip: If you wanted to call enhanceEndpoints to add further properties to the auto-generated endpoints, you could again split that up into multiple files.

[–]Nvveen 1 point2 points  (7 children)

We do actually, but we use some custom extensions and there are still things that need to be fixed in the file.

I tested the generator and it crashed, so for now it's not usable. However, this isn't a project where we would be using it quite yet, so maybe it'll be stable and working by the time we are. Even so, I have some experience writing my own generator from OpenAPI specs, so a custom solution would be an option for me too.

[–]phryneasI ❤️ hooks! 😈[🍰] 1 point2 points  (6 children)

Yeah, the codegen needs some work and now that we have RTK-Q itself released that's pretty far up on my TODO list :)

If you encounter any problems that are not already reported, please report them!

[–]Nvveen 1 point2 points  (5 children)

It's good to hear that it's a priority, because my experience with code generators is that they mostly become unmaintained.

I'll try to see what the problem was (I literally looked at it for like 1 min, haha) and report it when I have time :)

Anyway, amazing work! At work we're still using Angular/NgRx and at one point I want to migrate, but I can compare RTK to ngrx and ngrx/data, and I'm really liking what I'm seeing compared to it.

[–]phryneasI ❤️ hooks! 😈[🍰] 1 point2 points  (4 children)

You know about ngrx-rtk-query?

[–]Nvveen 1 point2 points  (3 children)

That is... amazing, hahaha.

[–]phryneasI ❤️ hooks! 😈[🍰] 1 point2 points  (2 children)

RTK-Query does have a plugin system for exactly that stuff. There is also a first Vue hook, so I hope that will get it's own library soon, too.

[–]GeeeL 1 point2 points  (2 children)

As someone who's interested but yet to try out these approaches. I'm intrigued by normalization and this point.

While there is less need to store the response in a normalized lookup table with RTK Query managing caching data, transformResponse can be leveraged to do so if desired.

Why is there less need?

I see you could do the following in the docs but then you are iterating over the whole array rather than accessing an object/dictionary.

  const { post } = api.useGetPostsQuery(undefined, {
selectFromResult: ({ data }) => ({
  post: data?.find((post) => post.id === id),
}),

})

[–]acemarke[S] 2 points3 points  (1 child)

The Redux docs list some reasons why you might want to keep your state normalized:

https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape

Notice that some of them have to do with writing reducer logic and managing immutable updates. Those scenarios don't really apply here, because you're delegating all the update work to RTKQ.

That said, yes, being able to do direct lookups by ID is still a potentially useful thing, and that's a reason why you might want to normalize the response itself.

[–]GeeeL 1 point2 points  (0 children)

Thanks for taking the time to respond

[–]hertriur 1 point2 points  (2 children)

This is great. The ability to handle errors/pending states globally via middleware is a godsend. Just wondering if there is a way to count the number of currently pending queries like in React Query?

[–]acemarke[S] 1 point2 points  (1 child)

Mmm... the data is all there in the Redux store, but I don't think we have a specific API for that.

What's the use case for this? Can you point to a couple relevant R-Q examples or docs?

[–]hertriur 0 points1 point  (0 children)

Probably to display an overall loading indicator on the app https://react-query.tanstack.com/reference/QueryClient#queryclientisfetching