How to put RTK query into store/slice? - reactjs

I just started using RTK query, but facing a problem
My app has all the setting in one big JSON, so I create an RTK query to fetch the setting. In the tutorial, the examples are about using the useQuery hook in the component and immediately display the result, but because the setting is so big that it has multiple pages/components to display it and allow the user to change the setting
Because the setting needs to be changed, so I think it's a good idea to put the state in the store, my question is how can I put the API result into the store?
I can see a couple of methods around it
Use useQuery in a component and when it has resulted, then do a dispatch to put the data onto the store
put useQuery from API in all components use it and use it to get cached data, then there is no need to put it on the store and use the component state for setting changes (I'm not sure about this approach as there are a lots of settings - even divided across different pages, it doesn't seems right use component state to manage it)
I'm new to RTK and RTK query, just wondering what's the best/good approach?

Using RTK Query implicitly means "putting your data in the store". It creates a store slice that manages all your data for you.
You can just use useQuery wherever you need that data and you either get it out of the store or request it new when necessary.
Most of the time you should not copy that data elsewhere as you will have to do stuff like manual cleanup etc. then.
I can also recommend going through the tutorial chapters for RTK-Q that were just added to the official Redux tutorial. They are very in-depth.
https://redux.js.org/tutorials/essentials/part-7-rtk-query-basics

From the docs:
However, when you use RTK Query, there is a mindset shift that happens. We're no longer thinking about "managing state" per se. Instead, we now think about "managing cached data". Rather than trying to write reducers ourselves, we're now going to focus on defining "where is this data coming from?", "how should this update be sent?", "when should this cached data be re-fetched?", and "how should the cached data be updated?". How that data gets fetched, stored, and retrieved becomes implementation details we no longer have to worry about.

Related

Should RTK query only be used to fetch data that doesn't need to be cached?

I'm getting back to using React now after a long time of not touching it, and I am looking at the redux toolkit which I've used before, and I see RTK query.
I'm trying to understand it, and think I have, but there's one thing I'm not sure if I'm missing or it is just designed to work like that.
Do we only use the RTKQ to fetch data that we don't need cached at all? For example loading a specific post that the user clicked on? I'm asking because in any tutorial I've watched or read I haven't seen any interaction with the other slices.
If for example I need to get some information that needs to be kept (otherwise I'd just keep fetching it), then I will use the regular createAsyncThunk?
It's the other way around entirely.
RTK Query is only for caching fetched data.
It's always been common to fetch data from the server and put it into the Redux store, but it takes a lot of work. You have to write thunks that make the request, dispatch actions based on the results, and write reducers that manage loading status and save the cached data. Then you have to write useEffect hooks to dispatch those thunks from components, and selectors that read the data from the store.
RTK Query does all of that work for you, automatically. And it then improves on those basics, by fetching new data when arguments change, refetching query data automatically when you send an update to the server, managing cache lifetimes and removing old data when components no longer need it, streaming updates, and much much more.
I'd recommend going through the relevant sections of the official "Redux Essentials" tutorial in our docs, as well as the RTKQ usage guides:
https://redux.js.org/tutorials/essentials/part-7-rtk-query-basics
https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced
https://redux-toolkit.js.org/rtk-query/usage/queries

What's the actual need for RTK Query when fetch API is provided?

Initially the idea of Flux is pretty obvious. The view gets Actions and then emits events based on the Action with which the store is updated. However, when I read through redux-toolkit it seems there are multiple ways of achieving something that should be trivial, namely getting JSON data from an endpoint:
Use the fetch API to get some data from an endpoint
Use RTK query to do the same
Use the React Context API
Using the useEffect() hook
Why are there so many different ways for achieving the simplest task?
Thanks.
This question hides a lot of sub-questions, so I'll try to answer them all as per my experience ( I'm partially a fan of RTK Query ).
To ask for a resource via HTTP from a browser there are not many ways, you have to use xhr or fetch API.
xhr is the old callback-based API, fetch is the modern standard promise-based API.
On top of these APIs many libraries were born to make the process and configuration easier/wider, today you mostly will work directly with fetch or with some gold-standard libraries like axios which is pretty similar to fetch but it has some cool extra features to handle the fetch calls and configuration.
On modern frameworks/libraries like React the problem is not How to retrieve a resource via HTTP, but how to handle the asynchronous call and the side effects that the response creates to the whole application.
So now this opens up to the several options you enlisted.
Basically for very basic fetch calls, you can handle the call inside
a useEffect if it's a GET call that has to retrieve data to show
inside the component that just mounted automatically, or
imperatively start the fetch on a button click for example if it's a
( usually ) POST,PUT, ecc... call.
You can then save the result in a React state, and furthermore pass it throughout your application with a context to let other components access the received response. By the way this leaves you with a lot of other behaviours uncovered, the first one is the loading state during the call, and the error management in case of bad status response or other exceptions. That's surely something you can implement briefly with a useState or better a useReducer hook, by the way, so that's not the real reason why you will feel the need for some async calls manager like RTK Query.
REVALIDATION
What happens if for example you are doing a post call on login of a user that returns the user object with user data, you save that data in a client side state manager ( or React state ) and use that state to display user data in your whole application. During the usage of your application, the user can perform some operations that change his data on DB, so you have to take care everytime he does that, to refetch his data and update his clientside data object, or to return the new user object from each POST/PUT/PATCH etc... call and update the clientside state which is pretty error prone, since you might find yourself having an unsynced clientside/serverside data state if you forget to update the clientside state after each operation, or if there's some issue during these phases. This can often lead to pretty hard to manage and to debug situations.
I'd say this is the First good reason to start using a Fetch manager like RTK Query.
This manager implements a pretty straightforward Tags based revalidation mechanism, that let's you to assign a tag to each Query ( GET ) that you perform, so for example you will make a query when the user logs in that retrieves the user data and you will tag the data with providesTags: 'User'.
Now you can safely perform any number of mutation on your user data on backend, and to each one, clientside, you will tell that as soon as it gets a response, it has to revalidate a certain Tag ( or multiple ones ). For example you might have an editPasswordMutation that changes the user password inside the database, and you will just add a invalidatesTags: 'User', this means that as soon as the user receive a positive from the server confirming that he updated the password, the client knows it has to reexecute the getUserData call, get fresh user data, and save them into the redux global store, so they will be immediately accessible to all the application components that are consuming them.
Totally authomatic.
This whole mechanism would definitely need you to write much more code if you were to implement it yourself.
OPTIMISTIC UPDATES
Most of times you will find yourself not wanting yor UI to wait for server response before showing the effect of a ( for example ) button click. Imagine when you click a thumb up on Facebook, you immediately see it in your UI, and that's not cause they have super fast servers, but because the UI is assuming that the call succedeed as soon as you click the button so it instantly adds a +1 to the likes count of the showed post.
This behaviour is not trivial, since usually when you show data in your application you have and should have a single source of truth. So for example if you are rendering a list of blog posts, you should have retrieved them from your backend database, saved them in your clientside state manager or directly render them as-is. If the first post has, let's assume 10 likes, you will render them based on data.posts[0].likes.count. So that's your source of truth. If you want to add a Like/Dislike button, and you want to implement an optimistic update, you would have to manually alter that source of truth, which is not good at all since it could be shared across multiple components ( in case of a global state ) or to duplicate it, and alter the duplicate, but that's not good either.
It's almost never ( almost ) a good idea to duplicate a source of truth.
RTK Query to the rescue!
With RTK Query Optimistic Update is pretty straightforward to set up, basically EVERY fetch can implement an optimistic update behaviour, since you can use the life-cycle method onQUeryStarted to intercept the immediate start of a call, and then use the utility : updateQueryData to update the data that your mutation is going to invalidate. Extremely SMART.
So in this case you will just have a simple mutation that will add a like to the blog post, that mutation will invalidate the posts data, the single post will be immediately updated in the UI with a +1 like, meanwhile the server will refetch the posts list ( since they were invalidated ) and fresh data in sync will be retrieved, and the user will notice just a pretty smooth experience.
There are many other features, especially involving Cache management, which are a plus, ( even if sometimes they can create some issues ) but I highlight these two main features that alone pushed me to become a user of RTK Query, it might look a bit verbose at start, but it definitely makes things pretty clean and tidy, coherent with the Redux philosophy.
As per docs, the goal of the tool is
It is designed to simplify common cases for loading data in a web application,
eliminating the need to hand-write data fetching & caching logic yourself.
It provides some caching and I personally find it very convenient to have declarative isLoading/error without the need to useState for all of it every time : )
But again, it's only optional: RTK Query is an optional addon included in the Redux Toolkit package.
There are multiple ways to achieve the same goal indeed. There's no gold standard in React world regarding this, for better or worse.

Redux Toolkit: What is the Preferred Method for Data Fetching?

I'm currently using React (with hooks) and Redux-Toolkit. After reading through the docs, I've come across Redux Thunks, createAsyncThunk, and RTK Query. Out of these three methods, which is the best for fetching data in a Redux slice, modifying that data within the slice, and then dispatching said data to a React?
Thanks in advance for your answers!
Each of them serves its own purpose, and only RTK-Q is made exactly for data fetching and caching.
The "problem" is that it's
Not really integrated with the application store's Redux and Thunks by default - it "lives" in its own store, with its own cache state, actions, and middleware.
The idea is that you don't want to put fetched data in your app-store, course, it will just duplicate the data and add another "source of truth" with the following inconsistency.
RKT-Q adds a concept of "API state", which represents the data on the backend, and should be handled significantly differently than App's (frontend) state.
In extra cases, you may handle the RTK-Q-related actions, handle them in your middleware (Thunks in your case) and update your Redux app-state producing actions with some calculated data\flags, etc.
Not covering all the cases of Application <-> API negotiation.
As an example - file\data export, API action calls, and other stuff that is out of RESTfull architecture. If you'll try to download a file from API via RTK-Q you'll find how bad it's covering such a case, and you'll be forced to use separate flow via Thunks\Sagas or\with plain fetch API to do so.
But still, RTK-Q brings you so much out of the box, so it is worth using it by default for data fetching, even with some work to handle several edge cases.
Wrapping up - I believe that there is no right\complete answer to your question and just more things to consider for your own app architecture design.

invalidateQueries vs setQueryData in graphQL mutations with react-query in a single page React app?

I have a graphQL API. That means I can customize whatever response I want from my mutations.
Of course, I need my UI to be in sync with fresh data. I'm not sure what would be the best option to accomplish that, though.
Option 1
https://react-query.tanstack.com/guides/invalidations-from-mutations
Not return anything from the mutations other than a success status
Use invalidateQueries to refetch related queries that should be stale after the mutation
Option 2
https://react-query.tanstack.com/guides/updates-from-mutation-responses
Return the full mutated object from the mutation
Use setQueryData to merge mutation response to current query data
What are the pros and cons of each a approach? Is one recommended of the other, when dealing with a graphQL backend?
React query seems to suggest that invalidateQueries should be the best approach. See quote below. I mean, I can have the object returned from the mutation, but it's not something "automatic" in a graphQL flexible endpoint. You can choose.
When dealing with mutations that update objects on the server, it's common for the new object to be automatically returned in the response of the mutation. Instead of refetching any queries for that item and wasting a network call for data we already have, we can take advantage of the object returned by the mutation function and update the existing query with the new data immediately using the Query Client's setQueryData method:
I think that not returning from the mutations and using invalidateQueries will make my mutations more simple and flexible, because they can be used in different contexts in invalidate different queries in each situation.
I guess that other then avoiding one extra network request, I don't see any other pros for going with Option 2.
This is for a React single page web app. What would be the best practice here?
Just found a very nice post about it in the official docs.
https://react-query.tanstack.com/community/tkdodos-blog#12-mastering-mutations-in-react-query
Which links to:
https://tkdodo.eu/blog/mastering-mutations-in-react-query
I guess the default would be to just go with invalidateQueries and use setQueryData if you really have to.

What are your best practices for preloading initialState in your Redux apps?

I'm currently not happy with the hacks I've come up with using Flux, and now Redux for populating initialState in my store.
I always send a payload of JSON down with the initial page load. Universal JavaScript is not an option in some of my applications as they are not all backed by Node. I would like to focus on "best practices" and patterns around how the data is preloaded on the client. Then, I feel that moving to the server is trivial.
Here’s a gist of what I’m currently doing at work and in a few other toy apps using Redux:
https://gist.github.com/kevinold/5767bb334472b7e2bfe3
To summarize:
I’m sending global variables down with the page
In componentDidMount() I'm dispatching actions that “receive” and handle the various pieces of data. (These “receive” methods are used when processing these bits of data when they are fetched async via a request action, and I’m repurposing them here since I already have the data.)
This works and there aren’t any issues that we’re running into, but it feels like a hack I’m not really happy with. Also, I feel that the initial state of Redux isn’t happy with me doing it this way.
According to http://rackt.org/redux/docs/recipes/ServerRendering.html and the section “The Client Side”, I am able to pass initialState to my store.
Here’s a patch I have for another project I'm helping with (KeystoneJS). I'm passing it's Keystone object into the initialState:
https://github.com/kevinold/keystone/commit/6f80c2f6f1e5c081361369a8bb31b75f1e62460f#diff-cd8e9933209e834b0519a0257bcfa914R8
While that does work, as you can see, I’m forced to match the shape of the output of my overall combinedReducers (https://github.com/kevinold/keystone/commit/6f80c2f6f1e5c081361369a8bb31b75f1e62460f#diff-b4b498ca92c4d05e050b45c725c26f9d) or I will get console warnings, etc.
I might be extremely lazy in dealing with this, but trying to limit having to update another piece of data when I add/change anything related to reducers (add a reducer, how they are composed, etc.).
I realizing I might have to in order to get this initialState into the reducers properly.
I'm currently digesting how (https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/client.js#L25) handles it's initialState.
I’d really appreciate some feedback on "best" and "real world" practices that are working for others.
I don’t fully understand the question but I’ll try my best to answer.
In componentDidMount() I'm dispatching actions that “receive” and handle the various pieces of data. (These “receive” methods are used when processing these bits of data when they are fetched async via a request action, and I’m repurposing them here since I already have the data.)
This is a bad place to dispatch them. If you choose to dispatch actions instead of prodiving initial state directly, do that before rendering. In other words, do that right after creating the store! This way you don’t render the initial blank state.
While that does work, as you can see, I’m forced to match the shape of the output of my overall combinedReducers (https://github.com/kevinold/keystone/commit/6f80c2f6f1e5c081361369a8bb31b75f1e62460f#diff-b4b498ca92c4d05e050b45c725c26f9d) or I will get console warnings, etc.
You’re not supposed to create the initialState object manually. You’re supposed to create a Redux store on the server, dispatch actions there to prefill it with data, and when it’s ready, call store.getState() to retrieve the state you want to pass down to the client. On the client, you’d read it from a global variable, and create the client store instance with it. In other words you never have to manually create initialState—you’re supposed to grab it on the server with store.getState(), pass it to client, and create a store with it.
Therefore I don’t understand the problem you describe. If your reducer names or nesting changes, it will change on the server too. There is nothing you need to do to “fix it”—if you use Redux both on client and server, their state structure will match.
I’d really appreciate some feedback on "best" and "real world" practices that are working for others.
If you use Redux on server, prefill the store as described above, and pass its state down to the client. In this case you need to use the initialState argument of createStore().
If you for some reason don’t use Redux on server but you want to prefill the data anyway (e.g. maybe you use something other than Node), the approach with dispatching actions is your next best bet because it doesn’t require your backend to know the state shape. In this case, you should pass those actions as JSON to the client, and dispatch() all of them right after the store is created.

Resources