Accessing cached data in RTK Query(new) - reactjs

Imagine we have 3 component.
first is index which is parent.
second is filter component and third one is table component.
I used mutations for filter some data and show them in table.
In filter component I did this:
const [filterSomeData] = useFilterSomeDataMutation();
const data = filterSomeData(myFilter);
Now I need to access data in table component.
Redux toolkit query with every request cache the result , how can I access that?

Generally: If you are receiving data from the server without triggering a change on the server, you should be using a query, not a mutation. Yes, you can do POST requests with Queries and the syntax is 100% the same as with mutations.
Then you should be using that useQuery hook in all components that need that data, with the same argument as you passed in initially. That means if you have something like a filter, that you should either pass that filter in by props (by lifting the filter state up to a common parent) or keeping that filter in a Redux slice and getting it from Redux before calling your query hook.
Since you are calling that useQuery hook with the same argument in multiple components, it will not make multiple requests, but reuse the response of the first request.

What you need is api.endpoints.foo.useLazyQuery() or api.useLazyFooQuery(). This way you can assess the last fetched data on that endpoint.
Read more from the official doc: https://redux-toolkit.js.org/rtk-query/api/created-api/hooks#usequerystate

As in RTK documentation is explained, the proposed solution is calling useQuery hooks with the same arguments as before it called. It retrieves data from cached enpoints, But as I need too many arguments to send to query hook, and I should send it to other component, so I prefer to use the store object to access data from endpoints as below:
const store = useStore();
const cachedQueries = store.getState().dashboardApi.queries;
Then I made an object with endpoint's name as key and their data as value:
let dashboardResult: { [key: string]: any } = {};
Object.values(cachedQueries).forEach((item: any) => {
dashboardResult = {
...dashboardResult,
...{ [item?.endpointName]: item?.data?.data }
}
});

Related

How to pass an argument in a React Query?

I need to pass a couple of arguments in a react query one of which needs to decided by the user action
Here is how the query looks so far:
const { refetch: likeDislikeProfile } = useQuery(
['like_dislike_profile'],
() => like_dislike_profile_q(data.userid, <BOOLEAN_ARG>), // 👈
{ enabled: false }
)
Whenever the clicks on a like/dislike button, the argument will be true/false respectively.
This is further used as a query param in the request : action?like=false
How do I achieve this?
My approach
create a local state that changes on button click
create a side effect (useEffect) method which is triggered when this state changes
which will further trigger this react query
This approach seems bad, can't think of anything else atm
Looks like your HTTP request is changing data in the backend, that's the use case for mutations.
From the official docs
A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server. If your method modifies data on the server, we recommend using Mutations instead.
For your use case it should be something like this
const updateLike = useMutation((id, bool) => like_dislike_profile_q(id, bool))
// invoke the mutation at any point like this
updateLike.mutate('my-id', true)
Read more about mutations on Tkdodo's blog post on mutations

How to share data across multiple components with react-query useMutation

I'm looking for a solution to share data across a React-Query mutation without having to create my own internal state or context.
I created a custom hook that takes care of the API call.
myData.ts
const useDataMutation = () => useMutation('MY_DATA_MUTATION', postData);
Then, I use my custom hook in different components.
Component1 is in charge of mutating. The response data will be available in data once the mutate is successful.
Component1.tsx
const { mutate, data } = useDataMutation();
useEffect(() => mutate('some_data'), []);
In another nested component, I want to access the data which came back from the response. But I don't want to pass down the data to 3-4 layers of components. And I wanted to avoid using a Context to access this data.
What I want is something like this:
Component2.tsx
const { data } = useDataMutation();
console.log({ data }); // log data once available.
But in this example, the data from Component2.ts is always undefined.
Is there a simple way to achieve something like this?
Thank you.
at the moment, mutations don't share data across instances like queries do. There is an open issue about it, and contributions are welcome.
If you're using #apollo/client then you can read the previously fetched data directly from the cache.
import Query from './query.ts'
const { todo } = client.readQuery({
query: Query,
variables: {}
})
That will not fetch the data from your server again and instead fetch it from the apollo cache. So you can fetch it using the hook or in the parent component and then 5 levels down you can just pull it again from the cache using the same hook.
When you're using a mutation it will update data in the cache if the properties in the query is the same as before, so if you have a query for getUser and a mutation for updateUser I think the mutation should automatically update the getuser cache data if the data aligns with the mutation data. I'm not sure about this.

react-query: is there a callback that get trigger regardless of whether getQuery is cached or not?

I want to do some side effects like setState and update context after the data is fetched. However, the onSuccess will not be executed when the data is in cache. Also useEffect doesn't work because if the data is cached, it doesn't change from undefined to the real data. Therefore it doesn't get trigger either. What's the best way of doing this? Thanks
My usecase is to extract some values from the data returned from useQuery and set a new state on those.
usually, they’re shouldn’t be a need to be a need to copy state from react-query into local state. This will just lead to duplication of the source of truth. It is best to keep them separated, so that you can also profit from background updates.
If you want to transform the data or subscribe to parts of the data, use the select option of useQuery:
const { data } = useQuery(key, fn, { select: data => data.map(...) })
Alternatively, you can compute some new data depending on the returned data with useMemo, e.g.:
const { data } = useQuery(...)
const articles = useMemo(() => data?.map(...), [data])
// work with articles from here on
You can also put that nicely in a custom hook.

Correct way to share one query result throughout the app

Let's say at the top of the app, we retrieve some basic information about the app or user before rendering the rest of the application:
const getUser = gql`
query getUser(id: Int!) {
user(id: $id) {
id
name
}
}
`)
function App({ data }) {
return (
<div>
{!data.loading && !data.error && (
// the application
)}
</div>
)
}
export default graphql(getUser, {
options: (props) => ({ variables: { id: props.id }})
})(App)
Now anywhere in the application, it is safe to assume that the user has been loaded and is stored. What is the proper way for another deeply nested component to the retrieve the user data without having to redo the querying and loading logic?
This is the very basic use of a store-based library like Redux. This is not the purpose to guide every step of the way here but you are looking for a single source of truth as described here: http://redux.js.org/docs/introduction/ThreePrinciples.html
In short:
Receiving getUser response should trigger a 'LOGGED_IN' action dispatching user Data, this would be catched by a reducer updating the user object in your store (as much nested as you want), a container would then connect to this user in the store and have all its data using connect()
As of now, I'm not certain there is a proper way, but these are the options I think are reasonable
Manually pass down data via props
Wrap your deeply nested component with the same query
Manual pass down ensures your components rerender correctly, but it can be a pain to refactor. Wrapping your nested component would just hit the cache. Yes, you probably need to redo the loading logic, but that's not a show stopper.
My advice is to manually pass down props for shallow nested components and rewrap deeply nested components. Unfortunately, react-apollo doesn't provide a convenient way to access the apollo-store for nested components the same way that redux's connect container does.

Where should I load data from server in Redux + ReactJS?

For example I have two components - ListOfGroupsPage and GroupPage.
In ListOfGroupsPage I load list of groups from the server and store it to the state.groups
In route I have mapping like ‘group/:id’ for GroupPage
When this address is loaded, the app shows GroupPage, and here I get the data for group from state.groups (try to find group in state via id).
All works fine.
But if I reload page, I'm still on page /group/2, so GroupPage is shown. But state is empty, so the app can't find the group.
What is the proper way to load data in React + Redux? I can see this ways:
1) Load all data in root component. It will be very big overhead from traffic side
2) Don't rely on store, try to load required data on each component. It's more safe way. But I don't think that load the same data for each component - it's cool idea. Then we don't need the state - because each component will fetch the data from server
3) ??? Probably add some kind of checking in each component - first try to find required data in store. If can't - load from the server. But it requires much of logic in each component.
So, is there the best solution to fetch data from server in case of usage Redux + ReactJS?
One approach to this is to use redux-thunk to check if the data exist in the redux store and if not, send a server request to load the missing info.
Your GroupPage component will look something like
class GroupPage extends Component {
componentWillMount() {
const groupId = this.props.params.groupId
this.props.loadGroupPage(groupId);
}
...
}
And in your action...
const loadGroupPage = (groupId) => (dispatch, getState) => {
// check if data is in redux store
// assuming your state.groups is object with ids as property
const {
groups: {
[groupId]: groupPageData = false
}
} = getState();
if (!groupPageData) {
//fetch data from the server
dispatch(...)
}
}
I recommend caching the information on the client using localstorage. Persist your Redux state, or important parts of it, to localstorage on state change, and check for existing records in localstorage on load. Since the data would be on the client, it would be simple and quick to retrieve.
The way I approach this is to fetch from the server straight after the store has been created. I do this by dispatching actions. I also use thunks to set isFetching = true upon a *_REQUEST and set that back to false after a *_SUCCESS or *_FAILURE. This allows me to display the user things like a progress bar or spinner. I think you're probably overestimating the 'traffic' issue because it will be executed asynchronosly as long as you structure your components in a way that won't break if that particular part of the store is empty.
The issue you're seeing of "can't get groups of undefined" (you mentioned in a comment) is probably because you've got an object and are doing .groups on it. That object is most likely empty because it hasn't been populated. There are couple of things to consider here:
Using ternary operators in your components to check that someObject.groups isn't null; or
Detailing in the initialState for someObject.groups to be an empty array. That way if you were to do .map it would not error.
Use selectors to retrieve the list of groups and if someObject.groups is null return an empty array.
You can see an example of how I did this in a small test app. Have a look at specifically:
/src/index.js for the initial dispatch
/src/redux/modules/characters.js for the use of thunks
/src/redux/selectors/characters.js for the population of the comics, series, etc. which are used in the CharacterDetails component

Resources