I'm using cache.writeQuery to update my cache after a mutation, and it just is not updating the cache. The updates just aren't in there.
This is in my update() callback from the useMutation:
const userData = copy(cache.readQuery({
query: GET_USER
}))
userData.me.categories.push(newCategory)
cache.writeQuery({
query: GET_USER,
data: userData
})
console.log('New category shows up here:')
console.log(userData.me.categories.map(category => category.id))
const _userData = copy(cache.readQuery({
query: GET_USER,
}))
console.log('New category is not in here:')
console.log(_userData.me.categories.map(category => category.id))
I'm deep-cloning my user data, and simply pushing onto its categories. It just doesn't want to store the updated data. It's clearly doing something, insofar as I can mutate all my existing categories and change their names etc, it just will not add a new one.
What can I try next?
Related
I have a route where i fetch all categories
getAllCategoriesAdmin: builder.query({
query: () => 'categories/allAdmin',
providesTags: ['CategoriesAdmin']
})
Then I get categories data from data refactored part of useGetAllCategoriesAdminQuery
const { isLoading, isError, data = {} } = useGetAllCategoriesAdminQuery();
Now I want to implement search field where I want to update that data part with new data in other words when I create new route
getAllCategoriesAdminSearch: builder.query({
query: () => 'categories/allAdminSearch',
providesTags: ['CategoriesAdmin'],
})
Is there any way that I can reupdate data part from useGetAllCategoriesAdminQuery so I dont have to make another data from useGetAllCategoriesAdminSearchQuery
const { data = {} } = useGetAllCategoriesAdminSearchQuery();
I don't get your question completely but I'm assuming you have a query API and you need the search query for this API. I hope this helps you.
there are many ways to re-fetch the data:
1:
I think your API should support some query params for searches like this:
getAllCategoriesAdmin: builder.query({
query: (searchInput) => `categories/allAdmin/?search=${searchInput}`,
providesTags: ['CategoriesAdmin']
})
RTK query creates a cache key base on query URL and params so if you change the search input it will automatically create a new request and fetch the data.
2:
another way is using invalidateTags for example if you have a mutaion query you can invalidate the tags in this case CategoriesAdmin and that causes the RTK clear the cache for corresponding query and refech the data.
3:
refetch function. every useHookQuery in RTK has a re-fetch function that you can assign it to your search event for re-fetching the new data.
const { refetch, isLoading, isError, data = {} } = useGetAllCategoriesAdminQuery();
I'm trying to extract selectors from queries in my apiSlice as said in this documentation: https://redux.js.org/tutorials/essentials/part-8-rtk-query-advanced
The documentation put this example:
const usersAdapter = createEntityAdapter()
const initialState = usersAdapter.getInitialState()
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getUsers: builder.query({
query: () => '/users',
transformResponse: responseData => {
return usersAdapter.setAll(initialState, responseData)
}
})
})
})
export const { useGetUsersQuery } = extendedApiSlice
// Calling `someEndpoint.select(someArg)` generates a new selector that will return
// the query result object for a query with those parameters.
// To generate a selector for a specific query argument, call `select(theQueryArg)`.
**// In this case, the users query has no params, so we don't pass anything to select()**
export const selectUsersResult = extendedApiSlice.endpoints.getUsers.select()
const selectUsersData = createSelector(
selectUsersResult,
usersResult => usersResult.data
)
export const { selectAll: selectAllUsers, selectById: selectUserById } =
usersAdapter.getSelectors(state => selectUsersData(state) ?? initialState)
Now, i need to have selectById selector (that by default takes userid as second parameter).
I can't manage to have a working selectById selector when my query looks like this
endpoints: builder => ({
getUsers: builder.query({
query: (applicationId) => `/application/${applicationId}/users`,
transformResponse: responseData => {
return usersAdapter.setAll(initialState, responseData)
}
})
})
How do I extract selectById selector from adapter and how i use it in a component with useSelector when I have this kind of query with arguments?
Thanks anyone that will help me
I feel like you mismatch concepts a bit, so it leads to confusion.
First of all, let's clarify, that's selectors you a using- it's not a selector to your state in usersAdapter, but to RTK-Q's own state.
By handling a response in transformResponse you are just copying the data from it to your's, usersAdapter's state.
So, considering that, you should be specific about which state you are going to select from. If you want it from the "final destination", i.e. from the adaptor's state, it's should be done via:
const usersSelectors = **usersAdapter**.getSelectors(
(state) => state.users // or something like that
)
Otherwise, using the selectors from apiSlices like extendedApiSlice from your example - you are fetching the data from RTK-Q's cached state, which may not contain some old data after the cache invalidation. If it's still your goal, the limitation is that RTK-Q's store isn't a normalized store you may expect, with ids and values, but rather the key-value pairs, where keys are your requests, and the values - last results (users arrays in your case). So, if you have no API endpoint defined for selecting a particular user by ID, you won't be able to select it from RTK-Q's state directly. But you may select the cached users by applicationId, and find your user by id in the result array. I bet it's not what you actually want, so most probably you need just to prepare selectors for your own store, as I've mentioned above.
RTKQ I'm creating a mutation. I'm trying to update it with the optimistic update method, but in the examples I've found, the scenario is always like this. postEdit mutation works. getPost request is made in onQueryStarted and it is finalized. In my scenario, instead of sending a getPost request, postEdit returns the current post value as a response if the statusCode is positive. In this case, I couldn't find how to update the store, do you have any ideas?
That's not the right way to use RTK Query or any other fetch library that handles revalidation for you like useSWR. You want to retrieve data from a query and perform revalidation after mutations on that data so the query gets automatically re-executed after each mutation and your UI is updated.
Let me give you this example I used in one of my applications:
addPost: builder.mutation({
query: (body) => {
console.log('ADDING POST', body)
return {
url: `add-post`,
method: 'POST',
body,
}
},
invalidatesTags: ['Posts'],
onQueryStarted(body, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
socialApi.util.updateQueryData(
'getPosts',
{ _id: body.profileOwner_id, limit: body.limit },
(draft) => {
console.log('POSTS ARRAY DRAFT', draft)
draft.unshift({
...body,
shareable: true,
title: 'published a new post.',
timestamp: new Date(),
likes: [],
})
}
)
)
queryFulfilled.catch(patchResult.undo)
},
}),
In this case I invalidate 'Posts' which triggers the query to User's Posts after the user adds a new Post. No need to add the new post manually in response to the mutation.
And as you can see, this way I can use the optimistic update util method .updateQueryData(), "draft" is the Posts list, I just add a new Post object to that array and that is automatically rendered as soon as the mutation is triggered.
I have a code like this
const [inputComment, setInputComment] = useState('');
const [
commentPost,
{ data: data4, loading: loading4, errorCreate4 },
] = useMutation(COMMENT_POST);
const { error: error2, loading: loading2, data: data2 } = useQuery(
GET_POST_BY_ID,
{
variables: {
postid: item.id,
},
},
);
const doComment = () => {
commentPost({
variables: {
postId: item.id,
userEmail: email,
comment: inputComment,
},
})
.then(({ data }) => {
setInputComment('');
console.log('success');
})
.catch((e) => {
console.log('not success');
});
};
This is supposed to get the data, and when I do comment then it runs the mutation and re-render everything.
My problem is, it re-render alright BUT the data that the useQuery fetch is not the newest data a.k.a the data before I add a new comment.
Does anyone know how to fix this problem??
Please help :(
Your mutation modifies data on the server side.
Once your mutation is done, you should refetch your data in order to get the modified version in your local cache on the client side.
By guessing how your mutation and query actually work, here is how it would look like:
const [
commentPost,
{ data: data4, loading: loading4, errorCreate4 },
] = useMutation(COMMENT_POST, {
refetchQueries: [
{ query: GET_POST_BY_ID, variables: { postid: item.id } }
]
});
Otherwise, intead of refetching from the server, you could update the local cache directly.
More info can be found here in the official documentation.
I assume commentPost is an insert operation, not an update of a single record. In this case, Apollo useMutation will not update the cache for you. You need to modify the cache yourself. The official Apollo documentation has covered this use case with an example. You may want to revise the usage of writeFragment as well.
Below are directly from apollo docs on cache update for list fields.
In most cases, a mutation response should include any object(s) the
mutation modified. This enables Apollo Client to normalize those
objects and cache them according to their __typename and id fields (by
default).
...
When a mutation's response is insufficient to update all modified
fields in your cache (such as certain list fields), you can define an
update function to apply manual changes to your cached data after a
mutation.
const [addTodo] = useMutation(ADD_TODO, {
update(cache, { data: { addTodo } }) {
cache.modify({
fields: {
todos(existingTodos = []) {
const newTodoRef = cache.writeFragment({
data: addTodo,
fragment: gql`
fragment NewTodo on Todo {
id
type
}
`
});
return [...existingTodos, newTodoRef];
}
}
});
}
});
EDIT
I noticed another answer suggests using refetch, which is not a bad option for starters. However, updating the cache is the recommended approach over refetch. You can refer to the Apollo blog article When To Use Refetch Queries in Apollo Client.
Below are some quotes you should note from this article.
If you’re just getting started with GraphQL, I think the mental model of passing in the queries that you’d like to re-run after a mutation is an easy one to wrap your head around.
...
The advantage here is that this approach is straightforward. The disadvantage is that we’re fetching the entire list of data again when we might not need to.
...
For a more efficient use of bandwidth and network round-trips, we can rely on cache normalization and update functions.
Intended outcome:
I am trying to refetch a query after a mutation. This works perfectly everywhere but I have a problem with this particular query.
const alertsSettingsQuery = gql`
query alertsSettingsQuery($issuerRoleNames: [String], $receiverRoleNames: [String]) {
alertsSettings(issuerRoleNames: $issuerRoleNames, receiverRoleNames: $receiverRoleNames) {
...
}
}
`;
Component querying the initial datas without variables :
export default compose(
graphql(alertsSettingsQuery, {name: "alertsSettingsData"}),
...,
)(Administration);
Component refetching the datas without variables :
mutate({
variables: updatedAlert,
refetchQueries: () => [{query: alertsSettingsQuery}]
}).then((data) => {
...
}).catch(err => {
...
});
Actual outcome:
If I check the network tab, the datas are correctly refetched and updated but the component doesn't receive the updated datas.
If I use the apollo devtools to see what's in my cache, I have this for the original datas :
And this from the refetch :
It seems like the two datas are not identified the same way.
If I change my refetchQueries to this :
refetchQueries: () => [{query: alertsSettingsQuery, variables: {issuerRoleNames: null, receiverRoleNames: null}}]
It works as expected.
I don't understand why I need to explicitly set the variables to null here, since I don't do that for any other refetch.
Version
apollo-client#2.3.4
react-apollo#<2.1.5>